架构简洁之道-组件构建原则

组件构建原则

如果说 SOLID 原则是用于指导我们如何将砖块成墙与房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。依赖关系管理的指标。

组件

组件的定义

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

  • 写死内存位置
  • 重定位技术
  • 连接器
  • 物理层大幅度升级

动态链接文件所使用的就是软件架构中的组件概念。组件化的插件式架构已经成为软件的构建形式了

组件聚合

构建组件的基本原则

  1. REP: 复用/发布等同原则·
  2. CCP: 共同闭包原则
  3. CRP: 共同复用原则

REP 复用、发布等同原则

软件复用的最小颗粒度应等同于其发布的最小颗粒度
要求组件的开发由某种发布流程来驱动,并且有明确的发布版本号。能够产生适当的通知和发布文档。

架构层面原则:组件中类与模块必须是彼此紧密相关的。一个组件不能由一组毫无关联的类和模块组成,他们之间应该有一个共同的主题或者大的方向。

CCP 共同闭包原则

我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。SRP 单一职责原则。

CCP 和 OCP 也是紧密相关的。CCP 讨论的就是 OPC 中的闭包。OCP 原则认为类应该便于扩展而考据修改。

共同闭包原则主要作用就是提示我们要将所有可能会被一起修改的类集中在一处。需要根据历史经验和预测能力,尽可能的讲需要被一同变更的那些点聚合在一起。

将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同修改的东西分开。

CRP 共同复用原则

不要强迫一个组件的用户依赖他们不需要的东西
CRP 帮助我们决策类和模块归属于哪一个组件的原则。原则:将经常共同复用的类和模块放在同一个组件中。
作用:告诉我们应该将哪些类分开。每当一个组件引用了另一个组件,就增加了一条依赖关系。
原则:不是紧密相连的类不应该被放在同一个组件里。

与 ISP 原则的关系

CRP 原则实际上是 ISP 原则的一个普适版。ISP 原则是建议我们不要依赖带有不需要的函数的类,而 CRP 原则则是建议我们不要依赖带有不需要的类的组件。共同点:不要依赖不需要用到的东西。

组件聚合张力图

REP 和 CCP 原则是粘合性原则,让组件变得更大
CRP 是排除性原则,让组件变小

Dingtalk_20221214180805.jpg

一个软件项目的重心从三角区域右侧开始,向左侧区域滑动。组件结构设计根据项目开发时间和成熟度不断变化。

组件解耦

无依赖环原则

组件依赖关系图中不应该出现环。
针对不同修改代码导致的各种冲突不兼容问题有两种解决方案

  1. 每周构建
  2. 无依赖环原则(ADP)

每周构建

每周前4天自己开发自己的,第五天进行统一构建。
优点:程序员放手干活儿
缺点:项目大的时候,一天未必可以完成集成工作,周六需要进行加班。
慢慢减少的构建次数,延长了项目被构建的时间间隔,会影响到项目质量加大风险。

消除循环依赖

将项目划分为一些可单独发布的组件,组件交由单人或者某一组程序员独立完成。有其他团队用通过发布机制通知其他程序员,并打版本号,且放到共享目录。组件开发者可以修改自己的私有版本。
这个开发流程,必须控制好组件之间的依赖结构,不循序结构中存在着循环依赖关系。

有向无环图 (Directed Acyclic Graph, DAG)

不管从哪个节点开始,都不能沿着这些代表了依赖关系的边最终走回到起始点。这种结构中不存在环。

Dingtalk_20221215111550.jpg

当组件结构依赖图中存在循环依赖时,想要按正确的顺序构建组件几乎是不可能的。

打破循环依赖

  1. 使用依赖反转原则(DIP)
  2. 创建一个新的组件,但是意味着需求变更

抖动

随着需求变更,组件结构也要随之变更。组件结构也会不停的抖动和扩张。所以必须要监控项目中的循环依赖关系,并消灭他们。有时候需要不可避免创建新组建,使得整个组件结构变得更大。

自上而下的设计

组件结构图是不可能自上而下被设计出来的,他跟随软件系统变化而扩张。组件依赖结构图不是用来描述应用程序功能的,更像是应用程序在建设性与维护性方面的一张地图。还需要使用单一职责原则(SRP)和共同闭包原则(CCP)来将经常变更的类聚合在一起。

组件结构图中重要目标是指导如何隔离频繁的变更。


稳定依赖原则 SDP

依赖关系必须要指向更稳定的方向
任何一个我们预期会经常变更的组件都不应该被一个难于修改的组件所依赖否则这个多变的组件也将会变得非常难以被修改。
通过稳定依赖原则,我们就可以确保自己设计中那些容易变更的模块不会被那些难于修改的组件所依赖。

稳定性

让软件组件难于修改的一个最直接的办法就是让很多其他组件依赖它。带有许多入向依赖关系的组件是非常稳定的,因为他的任何变都需要应用到所有依赖它的组件上。

稳定性指标

  • Fan-in:入向依赖,这个指标指代了组件外部类依赖于组件内部类的数量
  • Fan-out:出向依赖,这个指标指代了组件内部类依赖于组件外部类的数量。
  • I:不稳定性,I = Fan-out / (Fan-in + Fan-out)。该指标的范围是 [0,1],I=0 意味着组件是最稳定的,I=1 意味着组件是最不稳定的。

并不是所有组件都应该是稳定的

使用依赖反转 DIP 来优化掉对于不稳定组件的依赖

抽象组件

因为这些抽象组件通常会非常稳定,可以被那些相对不稳定的组件依赖。


稳定抽象原则 SAP

一个组件的抽象化程度应该与其稳定性保持一致
稳定抽象原则(SAP)为组建的稳定性与他的抽象化程度建立了一种关联。

  • 要求稳定的组件应该同时是抽象的,这样他的稳定性就不会影响到扩展性。
  • 要求一个不稳定的组件应该包含具体的实现代码,这样不稳定性就可以通过具体代码被轻易修改。

将稳定抽象原则(SAP)和稳定依赖原则(SDP)结合起来,就等于组件层次上的DIP。因为SDP要求的是让依赖关系指向更稳定的方向,而SAP则告诉我们稳定性本身就隐含了对抽象化的要求,即依赖关系应该指向更抽象的方向

高阶策略应该放在那里

代表了系统高阶策略的组件应该被放到稳定组件(I=0)中,不稳定组件中应该只包含哪些我们想要快速和方便修改的部分。
开闭原则:创造一个足够灵活、能够被扩展,而且不需要修改的类是可能的,而这正是我们所需要的。抽象类

衡量抽象化程度

  • Nc: 组件中类的数量。
  • Na: 组件中抽象类和接口的数量
  • A: 抽象程度,A = Na / Nc。

A 指标的取值范围是从0到1,值为0代表组件中没有任何抽象类,值为1就意味着组件中只有抽象类。

Dingtalk_20221222153913.jpg