目录

1.组件构建原则

1.1.组件

1.2.组件聚合

1.3.组件耦合

2.软件架构

2.1.什么是软件架构?

2.2.独立性

2.3.划分边界

2.4.策略与层次

2.5.业务逻辑

2.6.尖叫的软件架构

2.7.整洁架构

2.8.层次与边界

2.9.Main组件

2.10.测试边界

2.11.整洁的嵌入式架构

3. 总结


1.组件构建原则

1.1.组件

组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。

对于Java来说,宏观来看,它的组件是jar文件;微观来看,可以是单个类。常见的组件有:JavaBean、Spring组件等。

1.2.组件聚合

组件聚合是指哪些类应该被组合成一个组件。遵循三大原则:

复用/发布等同原则(REP)

本质:组件的发布和复用应该是等同的。具体来讲:在设计和实现组件时,应该将组件的复用和发布视为同一过程。也就是说,组件的设计应该考虑到它可能会被其他应用程序使用,并且应该尽可能地设计成可复用的、独立的、可测试的和可扩展的。

共同闭包原则(CCP)

本质:将那些一起变化的元素放在同一个组件中。具体来讲:在设计和实现组件时,应该将那些具有相同原因发生变化的元素放在同一个组件中,而将那些具有不同原因发生变化的元素分别放在不同的组件中。这样可以使组件更加独立、可维护和可复用。共同闭包原则是单一职责原则在组件层面的再度阐述。

共同复用原则(CRP)

本质:那些被多个组件一起使用的元素,应该被封装在一个共同的组件中。具体来讲:在设计和实现组件时,应该将那些被多个组件一起使用的元素封装在一个共同的组件中,而不是在多个组件中分别实现。这样可以提高代码的可重用性和可维护性,避免代码的重复实现。不要强迫一个组件的用户依赖他们不需要的东西。

1.3.组件耦合

无依赖环原则:

组件依赖关系图中不应该出现环。

消除循环依赖:

所谓有向无环图(DAG),是指不管我们从该图中的哪个节点开始,都不能沿着这些代表了依赖关系的边最终走回到起始点。也就是说,这种结构中不存在环。

假设某个新需求使我们修改了Entities组件中的某个类,而这个类又依赖于Authorizer组件中的某个类,会形成循环依赖。如何去打破循环依赖呢?

1. 应用依赖反转原则:创建一个User类需要使用的接口,然后将这个接口放入Entities组件,并在Authorizer组件中继承它。

2. 创建一个新的组件,并让Entities与Authorize这两个组件都依赖于它。将现有的这两个组件中互相依赖的类全部放入新组件。

架构整洁之道中篇(组件构建原则&软件架构)_java

 典型的组件结构图

架构整洁之道中篇(组件构建原则&软件架构)_架构_02

依赖关系反转 

架构整洁之道中篇(组件构建原则&软件架构)_业务逻辑_03

 让Entities与Authorizer共同依赖于一个新组件

稳定依赖原则:

本质是指依赖关系应该指向稳定的方向。具体来讲:在设计和实现组件时,应该尽量使组件之间的依赖关系指向稳定的方向。也就是说,稳定的组件不应该依赖于不稳定的组件,而不稳定的组件可以依赖于稳定的组件。

自上而下的设计:

自上而下的设计是一种分而治之的方法,将一个大型问题分解为更小、更容易管理的子问题。

2.软件架构

2.1.什么是软件架构?

一个好的软件架构应当做到可靠、易维护和可扩展,使软件系统更容易进行开发、测试、部署和维护。

优秀的架构师会小心地将软件的高层策略与其底层实现隔离开,让高层策略与实现细节脱钩,使其策略部分完全不需要关心底层细节,当然也不会对这些细节有任何形式的依赖。

2.2.独立性

按水平分层和用例解耦一个系统有很多种方式。例如,我们可以在源码层次上解耦、二进制层次上解耦(部署),也可以在执行单元层次上解耦(服务)。

源码层次:我们可以控制源代码模块之间的依赖关系,以此来实现一个模块的变更不会导致其他模块也需要变更或重新编译,这种模式叫作单体结构;部署层次:我们可以控制部署单元(譬如jar文件、DLL、共享库等)之间的依赖关系,以此来实现一个模块的变更不会导致其他模块的重新构建和部署。服务层次:我们可以将组件间的依赖关系降低到数据结构级别,然后仅通过网络数据包来进行通信。

按层解耦:架构师应该知道整个系统的基本设计意图。一个系统可以被解耦成若干个水平分层——UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库等。一个系统所适用的解耦模式可能会随着时间而变化。

2.3.划分边界

通过划清边界,我们可以推迟和延后一些细节性的决策,这最终会为我们节省大量的时间、避免大量的问题。

那么应在何时、何处画这些线?边界线应该画在那些不相关的事情中间。GUI与业务逻辑无关,所以两者之间应该有一条边界线。数据库与GUI无关,这两者之间也应该有一条边界线。数据库又与业务逻辑无关,所以两者之间也应该有一条边界线。

插件式架构:软件开发技术发展的历史就是一个如何想方设法方便地增加插件,从而构建一个可扩展、可维护的系统架构的故事。系统的核心业务逻辑必须和其他组件隔离,保持独立,而这些其他组件要么是可以去掉的,要么是有多种实现的。

为了在软件架构中画边界线,我们需要先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,而另一部分则是与核心业务逻辑无关但负责提供必要功能的插件。然后通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件。

架构整洁之道中篇(组件构建原则&软件架构)_用例_04

 插件式结构

2.4.策略与层次

“层次”是严格按照“输入与输出之间的距离”来定义的。也就是说,一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。

在一个设计良好的架构中,依赖关系的方向通常取决于它们所关联的组件层次。一般来说,低层组件被设计为依赖于高层组件。通过将策略隔离,并让源码中的依赖方向都统一调整为指向高层策略,我们可以大幅度降低系统变更所带来的影响。主要是:单一职责原则(SRP)、开闭原则(OCP)、共同闭包原则(CCP)、依赖反转原则(DIP)、稳定依赖原则(SDP)以及稳定抽象原则(SAP)的运用。

2.5.业务逻辑

业务逻辑是指软件系统中实现特定业务需求的程序逻辑,也就是软件系统中实现的业务规则和流程。

业务实体:关键业务逻辑和关键业务数据是紧密相关的,所以它们很适合被放在同一个对象中处理。我们将这种对象称为“业务实体(Entity)”。

用例:本质上就是关于如何操作一个自动化系统的描述,它定义了用户需要提供的输入数据、用户应该得到的输出信息以及产生输出所应该采取的处理步骤。

架构整洁之道中篇(组件构建原则&软件架构)_java_05

 用例示范

为什么说业务实体属于高层概念,而用例属于低层概念呢?因为用例描述的是一个特定的应用情景,这样一来,用例必然会更靠近系统的输入和输出。而业务实体是一个可以适用于多个应用情景的一般化概念,相对地离系统的输入和输出更远。所以,用例依赖于业务实体,而业务实体并不依赖于用例。

2.6.尖叫的软件架构

良好的架构设计应该只关注用例,并能将它们与其他的周边因素隔离。一个系统的架构应该着重于展示系统本身的设计,而并非该系统所使用的框架。

比如,一个住宅建筑设计的首要目标应该是满足住宅的使用需求,而不是确保一定要用砖来构建这个房子。

2.7.整洁架构

所谓整洁架构,具备以下特点:独立于框架(框架是工具);可被测试(业务逻辑);独立于UI(系统UI易变更);独立于数据库(数据库可替换);独立于任何外部机构。

源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。任何属于内层圆中的代码都不应该牵涉外层圆中的代码,尤其是内层圆中的代码不应该引用外层圆中代码所声明的名字。

架构整洁之道中篇(组件构建原则&软件架构)_用例_06

 以基于Web的、使用数据库的Java系统为例,其UML图如下:

架构整洁之道中篇(组件构建原则&软件架构)_业务逻辑_07

 

2.8.层次与边界

架构整洁之道中篇(组件构建原则&软件架构)_业务逻辑_08

 以Hunt The Wumpus游戏UML图为例,所有的箭头都是朝上的,GameRules组件就被放在顶层的位置,是最高层策略组件。

我们来考虑一下信息流的方向。首先,所有来自用户的信息都会通过左下角的TextDelievery组件传入。当这些信息被上传到Language组件时,就会转换为具体的命令输入给GameRules组件。然后,GameRules组件会负责处理用户的输入,并将数据发送给右下角的DataStorage组件。接下来,GameRules会将输出向下传递到Language组件,将其转成合适的语言并通过TextDelievery将该语言传递给用户。

这种设计方式将数据流分成两路。左侧的数据流关注如何与用户通信,而右侧的数据流关注的是数据持久化。两条数据流在顶部的GameRules汇聚。GameRules组件是所有数据的最终处理者。

2.9.Main组件

Main组件是系统中最细节化的部分——也就是底层的策略,它是整个系统的初始点。Main组件也可以被视为应用程序的一个插件——这个插件负责设置起始状态、配置信息、加载外部资源,最后将控制权转交给应用程序的其他高层组件。

2.10.测试边界

软件设计的第一条原则——不管是为了可测试性还是其他什么东西——是不变的,就是不要依赖于多变的东西。譬如,GUI往往是多变的,因此通过GUI来验证系统的测试一定是脆弱的。因此,我们在系统设计与测试设计时,应该让业务逻辑不通过GUI也可以被测试。

2.11.整洁的嵌入式架构

软件构建过程中的三个阶段:

1.“先让代码工作起来”——如果代码不能工作,就不能产生价值。

2.“然后再试图将它变好”——通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。

3.“最后再试着让它运行得更快”——按照性能提升的“需求”来重构代码。

3. 总结

本文介绍了组件构建原则和软件架构。

组件是是整个软件系统在部署过程中可以独立完成部署的最小实体。组件聚合可以通过三大原则:复用/发布等同原则、共同闭包原则(CCP)和共同复用原则(CRP);共同复用原则(CRP)。组件依赖关系图中不应该出现环。那么如何消除循环依赖?依赖反转原则或者创建一个新的组件。

软件架构要做到可靠、易维护和可扩展。按水平分层和用例解耦以维持独立性;边界线应该画在那些不相关的事情中间,从而构建一个可扩展、可维护的插件式架构;距离系统的输入/输出越远,它所属的层次就越高,一般来说,低层组件被设计为依赖于高层组件;业务实体是指关键业务逻辑和关键业务数据,适合被放到一个对象;用例定义了输入数据、输出信息以及处理步骤;良好的架构设计应该只关注用例,并能将它们与其他的周边因素隔离;所谓整洁架构,具备以下特点:独立于框架(框架是工具),可被测试(业务逻辑),独立于UI(系统UI易变更),独立于数据库(数据库可替换),独立于任何外部机构。