组件构建原则(五):稳定抽象原则

简介: 组件构建原则(五):稳定抽象原则

背景介绍


这是我的《架构整洁之道》系列的第十五篇,这篇文章的内容为稳定抽象原则。


本篇文章高度依赖本系列的上一篇文章,文中的 SAP 也是上一篇文章的稳定依赖原则。


《架构整洁之道》系列:

稳定抽象原则


一个组件的抽象化程度应该与其稳定性保持一致。


高阶策略应该放在哪里


在一个软件系统中,总有些部分是不应该经常发生变更的。这些部分通常用于表现该系统的高阶架构设计及一些策略相关的高阶决策


我们不想让这些业务决策和架构设计经常发生变更,因此这些代表了系统高阶策略的组件应该被放到稳定组 件(I=0)中,而不稳定的组件(I=1)中应该只包含那些我们想要快速和方便修改的部分。


那么就有一个疑问:


如果我们将高阶策略放入稳定组件中,那么用于描述那些策略的源代码就很难被修改了。这可能会导致整个系统的架构设计难于被修改。如何才能让一个无限稳定的组件(I=0)接受变更呢?


这里就可以回顾一下之前的开闭原则:设计原则(三):OCP 开闭原则。那么有了这个原则为指导,创造一个足够灵活、能够被扩展,而且不需要修改的类是可以实现的。


这时我们就可以用到抽象类。


稳定抽象原则简介


稳定抽象原则(SAP)为组件的稳定性与它的抽象化程度建立了一种关联。一方面,该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性。另一方面,该原则也要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改。


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


DIP 毕竟是与类这个层次有关的原则,而一个类要么是抽象类,要么就不是。SDP 与 SAP 这对原则是应用在组件层面上的,我们要允许一个组件部分抽象,部分稳定。


类只有两种可能,要么是抽象类,要么不是,而组件并不是“非黑即白”的,所以有了

下面的衡量标准:


衡量抽象化程度


假设 A 指标是对组件抽象化程度的一个衡量,它的值是组件中抽象类与接口所占的比例。那么:


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


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


主序列


我们现在有了组件的稳定性 I与其抽象化程度 A ,就可以来定义他们两者之间的关系了。


网络异常,图片无法展示
|


上图中最稳定的、包含了无限抽象类的组件应该位于左上角(0,1),最不稳定的、最具体的组件应该位于右下角(1,0)。我们不能强制要求所有的组件都处于(0,1)和(1,0)这两个位置上,那么就必须假设上图存在着一个合理组件的区间。而这个区间应该可以通过排除法推导出来,也就是说,我们可以先找出那些组件不应该处于的位置。


网络异常,图片无法展示
|


下面我们来一起了解痛苦区和无用区~


痛苦区


假设某个组件处于(0,0)位置,那么它应该是一个非常稳定但也非常具体的组件。

这样的组件在设计上是不佳的,因为它很难被修改,这意味着该组件不能被扩展。这样一来,因为这个组件不是抽象的,而且它又由于稳定性的原因变得特别难以被修改,我们并不希望一个设计良好的组件贴近这个区域,因此(0,0)周围的这个区域被我们称为痛苦区。


不可变组件「比如工具型类库」落在(0,0)这一区域中是无害的,因为它们不太可能会发生变更。正因为如此,只有多变的软件组件落在痛苦区中才会造成麻烦。


无用区


我们来看看靠近(1,1)这一位置点的组件,这些组件通常是无限抽象的,但是没有被其他组件依赖,这样的组件往往 无法使用,因此我们将这个区域称为无用区。


例如在系统的某个角落里某个没有人实现的抽象类,它们一直静静地躺在那里,没有人使用。


落在无用区中的组件也一定会包含大量的无用代码,这并不是我们希望出现的情况。


避开这两个区域


主序列线:从(1,0)连接到(0,1)。


坐落于主序列线上的组件不会为了追求稳定性而被设计得“太过抽象”,也不会为了避免抽象化而被设计得“太过不稳定”。这样的组件既不会特别难以被修改, 又可以实现足够的功能。


对于这些组件来说,通常会有足够多的组件依赖于它们,这使得它们会具有一定程度的抽象,同时它们也依赖了足够多的其他组件,这又使得它一定会包含很多具体实现。

在整条主序列线上,组件所能处于最优的位置是线的两端。然而,大型系统中的组件不可能做到完全抽象,也不可能做到完全稳定。所以我们只要追求让这些组件位于主序列线上,或者贴近这条线即可。


离主序列线的距离


D 指标:距离 D=|A+I-1|,该指标的取值范围是[O,1]。值为 0 意味着组件是直接位于主序列线上的,值为 1 则意味着组件在距离主序列最远的位置。


通过计算每个组件的 D 指标,就可以量化一个系统设计与主序列的契合程度了。如下图:


网络异常,图片无法展示
|


我们也可以跟踪一个组件随版本更迭 D 值的变化,假设 D=O.1 是组件的达标红线,R 组件的 2.1 版本的 D 值己经超出了红线范围,这就告诉我们现在值得花一些精力来找出这个组件偏离主序列线的原因了。


网络异常,图片无法展示
|


结束语


网络异常,图片无法展示
|


这样三条原则就结束了,他们的内涵主要为依赖关系管理的指标,可以被用来量化分析某个系统设计与“优秀”设计模式之间的契合度。


✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

少年向来不识天高地厚
放眼处皆自负才高八斗
虽是自命风流
倒也坦诚无忧
我爱这样的少年
谦和而狂妄
骄傲又坦然☀️

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

相关文章
|
5月前
|
存储 Cloud Native Linux
软件开发方法:复用与扩展
软件开发方法:复用与扩展
|
20天前
|
设计模式 存储 算法
揭秘模版方法模式-让你的代码既灵活又可维护
本文深入探讨了模板方法模式在软件开发中的应用。开篇通过介绍软件设计的挑战,引出模板方法模式的重要性。随后,文章展示了不使用设计模式实现时存在的问题,并通过一个重构示例,详细阐述了如何使用模板方法模式解决这些问题。本文还深入剖析了模板方法模式的工作原理,总结了其优点和缺点,并提供了最佳实战建议。此外,文章还讨论了模板方法模式与其他设计模式的结合应用,为读者提供了全面的视角来理解和应用这一设计模式。
22 0
揭秘模版方法模式-让你的代码既灵活又可维护
|
23天前
|
设计模式 API 数据库
【C/C++ 设计思路】C++中解耦策略的艺术:有效管理复杂依赖关系
【C/C++ 设计思路】C++中解耦策略的艺术:有效管理复杂依赖关系
51 3
|
6月前
软件设计原则-合成复用原则讲解以及代码示例
合成复用原则(Composition/Aggregation Reuse Principle,CARP)是面向对象设计的一种重要原则,也被称为组合/聚合复用原则。它强调通过组合(Composition)或聚合(Aggregation)关系来达到代码复用的目的,而不是通过继承关系。
79 0
|
6月前
|
测试技术
软件设计原则-单一置原则讲解以及代码示例
单一职责原则(Single Responsibility Principle,SRP)是面向对象设计中的一个重要原则,提倡将一个类或模块只负责一个职责或功能。它最早由Robert C. Martin在其《敏捷软件开发:原则、模式与实践》一书中提出。 单一职责原则的核心思想是:一个类或模块应该只有一个引起它变化的原因。也就是说,每个类或模块都应该只有一个职责或功能,并且该职责或功能应该在该类或模块内部封装起来,而不是分散到多个类或模块中。
35 0
|
6月前
|
设计模式 Java
Java设计模式七大原则-合成聚合复用原则
Java设计模式七大原则-合成聚合复用原则
48 0
|
7月前
|
设计模式 Oracle 关系型数据库
七大设计原则之合成复用原则应用
七大设计原则之合成复用原则应用
114 0
|
8月前
|
设计模式 Oracle 关系型数据库
软件架构设计原则之合成复用原则
合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)/聚合(contanis-a)而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
79 0
|
9月前
|
设计模式 Java uml
你的职责链模式符合五大原则吗?-系统学习九
工作之余对于用到的设计模式进行总结再梳理,发现职责链模式的妙处以及五大原则的指导下更能发挥职责链模式的优势于是乎便有了这篇博文的诞生
|
11月前
|
设计模式 测试技术 程序员
代码的简单设计五原则
代码的简单设计五原则
33027 1