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

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

背景介绍


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


本篇文章高度依赖本系列的上一篇文章,文中的 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 值己经超出了红线范围,这就告诉我们现在值得花一些精力来找出这个组件偏离主序列线的原因了。


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


结束语


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


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


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

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

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

相关文章
|
设计模式 前端开发 关系型数据库
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 3
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)
5583 2
|
前端开发 小程序 API
【微信小程序】-- 使用 npm 包 - API Promise化(四十二)
【微信小程序】-- 使用 npm 包 - API Promise化(四十二)
|
SQL 关系型数据库 MySQL
mysql 组复制
mysql 组复制
|
存储 Prometheus 监控
评估系统的可用性时间
评估系统可用性时间是指对系统在预定时间内正常运行的能力进行测量和分析,以确保其稳定性和可靠性满足用户需求。这通常涉及对系统故障率、恢复时间和维护周期的综合考量。
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
549 3
|
Dart 小程序 API
鸿蒙原生开发手记:01-元服务开发
元服务是鸿蒙系统中的一种轻量级应用形态,无需下载即可直接运行,类似于微信小程序但更轻量。使用原生开发,性能和体验更优。创建元服务需使用 DevEco 工具,支持深色模式和服务卡片功能,开发测试和上架流程详见相关文档。
799 1
鸿蒙原生开发手记:01-元服务开发
|
存储 搜索推荐 数据建模
Elasticsearch 的数据建模与索引设计
【9月更文第3天】Elasticsearch 是一个基于 Lucene 的搜索引擎,广泛应用于全文检索、数据分析等领域。为了确保 Elasticsearch 的高效运行,合理的数据建模和索引设计至关重要。本文将探讨如何为不同的应用场景设计高效的索引结构,并分享一些数据建模的最佳实践。
552 2
|
数据处理
什么是GPON 千兆被动光纤网络?
【4月更文挑战第14天】
1399 0
|
存储 算法 数据库
细数各大唯一id生成算法
一、序言几乎所有的业务系统,都有生成一个唯一id的需求,例如: 1.订单号2.活动id3.消息id这个记录标识往往就是数据库中的唯一主键,也可以作为唯一索引。这个记录标识上的查询,往往又有分页或者排序的业务需求,例如: (1)拉取最新的一页的聊天记录:select * by message_id/ order by gmt_create/ limit 100 (2)拉取最近的一百条流水:selec
808 0
细数各大唯一id生成算法
|
弹性计算 安全 虚拟化
万字干货分享 | 阿里云CIPU技术解析
2022年6月,阿里云发布了云基础设施处理器CIPU(Cloud  Infrastructure Processing  Unit),将其定义为取代传统CPU的新一代云计算体系架构的核心。在这个全新体系架构下,CIPU向下对数据中心计算、存储、网络等底层基础设施快速云化并进行硬件加速,向上接入飞天云操作系统,将全球数百万台服务器构建为一台超级计算机,实现资源的灵活编排和调度,给用户提供高质量弹性云计算算力集群。
万字干货分享 | 阿里云CIPU技术解析