Robert C. Martin 写过一篇有趣的文章,介绍了一组度量,可用于从设计中各子系统之间相互依赖的角度,衡量面向对象设计的质量。
下面是他在文章中关于模块间相互依赖的论述:
What is it that makes a design rigid, fragile and difficult to reuse. It is the interdependence of the subsystems within that design. A design is rigid if it cannot be easily changed. Such rigidity is due to the fact that a single change to heavily interdependent software begins a cascade of changes in dependent modules. When the extent of that cascade of change cannot be predicted by the designers or maintainers the impact of the change cannot be estimated. This makes the cost of the change impossible to estimate. Managers, faced with such unpredictability, become reluctant to authorize changes. Thus the design becomes rigid.
为了对抗僵化,他引入了传入耦合、传出耦合、抽象度和不稳定度等度量。
传入耦合(Afferent Coupling):本项目之外、依赖于本项目内部类型的类型数量。
传出耦合(Efferent Coupling)本项目内部类型所使用的、本项目之外的类型数量。
传出耦合和传入耦合也可以应用于命名空间和类型。例如,某个特定类型的传出耦合是它直接依赖的类型数量。TypeCe 很高的类型依赖了过多的其他类型。它们很复杂,通常承担着不止一项职责。
抽象度(Abstractness)
内部抽象类型(即抽象类和接口)数量与内部类型总数的比值。该度量的取值范围是 0 到 1,A=0 表示完全具体的项目,A=1 表示完全抽象的项目。
A = Na / Nc
Where:
A = abstractness of a module
Zero is a completely concrete module. One is a completely abstract module.
Na = number of abstract classes in the module.
Nc = number of concrete classes in the module.不稳定度(Instability)
传出耦合(Ce)与总耦合的比值。I = Ce / (Ce + Ca)。该度量是项目抵抗变更能力的指标。取值范围是 0 到 1,I=0 表示完全稳定的项目,I=1 表示完全不稳定项目。
I = Ce/(Ce + Ca)
I represent the degree of instability associated with a project.
Ca represents the afferent coupling, or incoming dependencies, and
Ce represents the efferent coupling, or outgoing dependencies抽象度-不稳定度图与“痛苦地带”
下面是 C++ POCO 库的抽象度-不稳定度图示例。

这张图背后的思想是:一个代码元素在程序中被使用得越广泛,它就应该越抽象。换句话说,避免过度依赖具体实现;转而依赖抽象。这里所说的热门代码元素,指的是被程序中其他项目大量使用的项目(不过这个思想同样适用于包和类型)。
让具体类型在整个代码库中被广泛使用,并不是好主意。这会在程序中形成“痛苦地带”(Zones of Pain)——在这些地方,修改实现可能会波及程序的大部分。而且众所周知,实现比抽象更频繁地演变。
上图中的主序列线(虚线)展示了抽象度与不稳定度应如何平衡。稳定的组件会位于左侧。查看主序列线可以发现:这样的组件应当非常抽象,才能接近理想的线——反过来说,如果它的抽象程度很低,就会落在一个被称为“痛苦地带”的区域。
使用 OOP 方法时,如何对抗僵化?
正如 Robert C. Martin 在他的文章中所写的,我们必须使用抽象类和接口,让项目更灵活,并降低代码元素之间的高耦合。
OOP 中的耦合可能通过以下方式引入:
- 继承:采用 OOP 范式时,继承经常被过度使用;遗憾的是,在许多情况下它会让代码更加僵化。一些设计模式有助于解决继承引入的僵化问题,比如适配器模式,它能把继承引入的僵化降到最低。
- 直接使用具体实现:在这种情况下,代码也会变得僵化,因为一旦出于某种原因需要换用另一个库或框架,代码就很难修改。与继承一样,也有一些设计模式可以尽量减少这种僵化,比如桥接模式(Bridge)或代理模式(Proxy)。
使用 OOP 方法时,建议掌握 GoF 结构型模式;它们有助于减少耦合引入的僵化。
