原文:之前@有个梨UGle… - @leonlucc的微博 - 微博 (weibo.com)
之前 @有个梨UGlee 发起了一个关于松耦合/紧耦合标准的大讨论,我当时提了一个观点是耦合度是对跨边界依赖的复杂度的描述。这里再对边界和依赖两个概念补充一下。
首先对边界的三种级别做一个阐述:
-
源码级
用命名空间、包或者类来区分不同的组件,组件运行于同一进程,边界属于一种源码级别白盒形式。跨边界的方式为直接调用其他组件的方法,迅速而廉价。是耦合度比较高的边界,如果所依赖组件出现问题,将无法通过编译。 -
lib 包级
用二进制或字节码形式的 lib 包作为组件的载体,组件运行于同一进程,边界外对边界内是黑盒。跨边界的方式通常为接口调用,但需要静态或者动态链接。静态链接在编译期完成,耦合度较高,所依赖组件出现问题同样无法通过编译。动态链接则耦合度适中,所依赖组件出现问题会影响部分逻辑的运行。 -
进程级
组件以进程的方式独立部署,边界外对边界内是黑盒,且完全独立。跨边界的方式为进程通信,有共享资源、事件消息、服务调用等或同步或异步的手段,通信成本和延时都较高。是耦合度最低的边界,要是细分的话,异步通信比同步通信耦合度更低。
有了边界,那么依赖就是系统中的跨边界调用。bob大叔在《Clean Architecture》中提到了和组件依赖关系有关的两个原则:
-
无依赖环原则 (THE ACYCLIC DEPENDENCIES PRINCIPLE)
即组件依赖关系图中不应该出现环。显然,系统中存在循环依赖的组件会增加耦合度。 -
稳定依赖原则 (THE STABLE DEPENDENCIES PRINCIPLE)
即依赖关系必须要指向更稳定的方向。一个组件的不稳定性可以用扇出公式结算:I = Fan-out / (Fan-in + Fan-out),对外依赖越多则越不稳定。
如果考虑单个组件的耦合度,那这个公式值得参考,虽然耦合度并不等同于不稳定性,出度为0也不代表耦合度为0,但至少对依赖的稳定性做出了量化。
如果要计算系统整体的耦合度,这个公式就不太适用,还得从组间两两之间的耦合度入手,一个简单的思路是,和所有两两组间依赖关系的数量和不稳定性成正相关。综上,如果要对耦合度做一个数学化的标准,可以尝试对边界的复杂度和依赖的复杂度进行量化,从而可以为解耦优化提供一个参考指标。
| 边界级别 | 特征 | 跨边界方式 | 跨边界通信 | 案例 | 耦合度 |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 源码级 | 用命名空间、包或者类来区分不同的组件,组件运行于同一进程,边界属于白盒形式 | 方法调用 | 高通信量,低延时 | 所有直接的类方法和函数调用 | 高 |
| lib 包级 | 用二进制或字节码形式的 lib 包作为组件的载体,组件运行于同一进程,边界外对边界内是黑盒 | 接口调用,需要编译期或运行期的链接 | 中通信量,低延时 | 1. 编译型语言的开发包
2. 编译型语言的动态链接库,解释型语言的模块或开发包 | 中 |
|进程级 | 组件以进程的方式独立部署,边界外对边界内是黑盒,且完全独立 | 同步进程通信:服务调用
异步进程通信:共享资源、事件信息 | 低通信量,高延时 | 1. 微服务 API
2. 信号量、消息队列 | 低 |
依赖原则 | 解释 | 耦合度 |
---|---|---|
无环依赖 | 组件依赖关系图中不应该出现环 | 与循环依赖的数量成正相关 |
稳定依赖 | 依赖关系必须要指向更稳定的方向 | 与依赖的数量和不稳定性成正相关 |