发布在即!企业 AIGC 应用程度测评,3 步定制专属评估报告。抢首批测评权益>>> 了解详情
写点什么

架构设计原则之我见(二):SOLID 原则

  • 2020-05-08
  • 本文字数:2787 字

    阅读完需:约 9 分钟

架构设计原则之我见(二):SOLID原则

SOLID 原则,据 WikiPedia 所说,是由 Robert C. Martin 总结的面向对象设计原则。这个名字其实是以下五个原则的首字母简写:


  • Single responsibility principle;

  • Open/closed principle;

  • Liskov substitution principle;

  • Interface segregation principle;

  • Dependency inversion principle。

“Single responsibility principle”

这句话翻译成中文是“单一职责原则”。这是一句缺乏主语的话,推断应该是指设计师所设计的系统吧。所以补充完整后,整句话的意思应该是:“设计师所设计的目标系统,其职责应该是单一的”。

如何判定“职责”是否“单一”?

判定“职责单一”的标准是什么难以回答,只能通过作者的文章进一步分析,尝试理解作者原意。


这个原则也并非 SOLID 原则作者原创,据作者原文所说:“This principle was described in the work of Tom DeMarco and Meilir Page-Jones . They called it cohesion”,原来这个原则来源于 Tom DeMarco 和 Meilir Page-Jones 两位前辈的工作,原本叫做“Cohesion”,也就是“内聚”。作者对“内聚”给出的解释是:“A class should have only one reason to change”。下文根据作者所给出的例子,来进一步理解作者的意图。


文章开头以一个保龄球游戏的编程设计来探讨这一原则。原本 Game 类有两个责任:一、负责跟踪当前帧,相当于打球;二、负责计算分数。作者认为,如果把这两个职责放在同一个类中,会引起耦合,因此要对 Game 作架构拆分,把这两个责任分别拆分给两个不同的类,并给出了拆分的理由:“Because each responsibility is an axis of change”,意思是“因为每个职责都是一个变化的维度”。猜想作者想表达的是,由于这两个职责是互相正交的维度,分拆开后,可以避免它们互相影响的意思。


这里其实有两个问题:


首先,两个职责放在同一个类中,并不代表会发生耦合。


耦合的意思是当一个职责内部发生变动时,会影响到另外一个职责的正常执行。假设把两个职责的代码糅合在一起,形成一个大的代码块,这当然是耦合的,此时修改任何一个职责都要小心,牵一发而动全身。


但是我们可以把这两个职责放在两个不同的方法中,比如拆分成 Game.trackFrame(), Game.calcScore()两个方法后,在修改其中一个职责时,只要输入输出的参数不发生变化,也并不会产生耦合。也就是说,要解决耦合这一问题,并非只有“拆分成两个不同的类”这一个解决方案,在同一个类中拆分成两个方法也可以解决,因为拆分成方法是拆分成类的前提。是否需要拆分成类,还需要有其他方面的考虑,解耦这一理由还不够充分,此处就不详细展开。


其次,很多人都忽略了为何两个职责可以被拆分开。


我们需要回到现实生活来分析保龄球游戏的核心生命周期。


在现实生活中打保龄球时,确实有算分这一环节。在每一次打球结束时, 机器会自动给出分数。当然,在早期没有机器时,这个分数肯定是由打球人自己来算的。为什么后来可以拆分出来交给机器来算呢?因为算分活动必须等待打球结束才能进行,打球与算分二者在执行时间上是属于完全不会发生交叉的两个连续动作,且打球的结果作为算分的输入,所以两个动作本来就是没有耦合的,可以拆分开,成为保龄球游戏生命周期中的两个相续活动。


这两个活动哪一个才是核心生命周期活动呢?可以看到,人们去保龄球馆是为了亲身体验打球,而不是为了体验得分。而且即使没有算分规则,人们也 可以玩的很开心,但如果没有打球的体验,只有算分规则,那么这个游戏也就不成立了。所以,这个游戏的核心生命周期是打球,而非算分。算分只是在打球结束后对结果的计算,属于非核心生命周,因此分数计算规则代码可以从打球代码中拆分出来,以保龄球游戏所产生的结果作为算分的输入来推动执行,形成树状结构。


而在拆分后,Game 的原本功能并没发生任何变化,只不过将其中一个步骤的实现代码分离出去了而已,然后通过方法调用,以直接获取结果的方式整合回归,还是同一个整体,没有发生变化。这一做法,使得 Game 能够更加专注于其本身的职责,分数计算自身也能更加专注,各自被修改时也可以互不影响。


所以,二者能够拆分开,并非“Because each responsibility is an axis of change”,而是因为其中存在非核心生命周期活动。并且拆分也并不仅限于拆分成类,首先应该能拆分成方法,这是拆分为类的前提。

“单一”与“内聚”

再从这个例子来分析“单一”的含义,确实还是叫“内聚”比较好。


从内聚的角度来看,在打球和算分两个方法拆分开后,trackFrame()与 calcScore()各自都专注于自身的业务,不受对方的影响,因此二者都是内聚的,自身都是完整的,只要给出输入参数就可以独立返回输出结果。而且 Game 这个类完整包含了保龄球自身的业务,其自身也是内聚的。


可是一旦改成“单一职责”,意思就发生了变化,着重点变成了“单一”。其后文在详细解释时,又把表述从“an axis of change”改成“one reason to change”, 意思进一步发生了变化:“an axis of change”指的是一个维度,而“one reason to change”指的是一个理由。二种表述区别很大,完全误解了“内聚”的本意,难怪会有很大的争议。


另外怎样才能算“职责单一”呢?这是没有确定标准的,需要相对于某个一个参考点才能确定是否单一。比如 Game 包含打球和算分两个步骤,难道 Game 的职责就不“单一”了吗?不是的。保龄球游戏需要打球和算分两个步骤,以组成一个“单一”的运动,放在一起正是为“单一”运动而服务的,这样做并不能说不“单一”。只有把对比的对象改为打球和算分时,才可以说 Game 的职责不单一。但是打球和算分本身就是从 Game 中拆分出来的,怎么可以拿整体相对其拆分出来的部分来比“单一”呢?这不合理。如果真的这么去比,即使把打球和算分二者拆分开后,算分的职责就“单一”了吗?也不是的,算分也可以拆分为很多不同的规则,在规则的层面看,算分的职责也并不“单一”,还需要再拆分!按照这个“单一职责”分拆下去,永远没有止境,陷入死循环。


所以“单一”是一个相对的词语,必须要看针对什么来说是“单一”的,不能单独来看。也不能因为一个事情分为两个步骤,就说这个事情不“单一”,因为这两个步骤所组成的是同一个事情,是单一的。而把这两个步骤拆分开后由两个人来分别执行,对于这两个人来说,各自的职责仍然是单一的,但是不能因此而否认二者所组成的原来那个事情不“单一”。正因为这两个人各自“单一”职 责的完成,组成了原本的那个“单一”的事情。


回过头来,如果读者明白“内聚”,站在“内聚”的角度来看“单一职责”原则, 来理解作者的“A class should have only one reason to change”这个解释,就可以秒懂作者只不过是想表达“内聚”而已。因此,读者千万不要真的从“单一职责”的角度去理解这个原则,会很容易产生误解,作者不过是想通过这一原则来表述作者所理解的“内聚”含义罢了。


掌握”内聚“,才是根本!

延展阅读

架构设计原则之我见(一):反思 KISS 原则


2020-05-08 11:172186

评论 1 条评论

发布
用户头像
很受启发,希望作者继续写下去。
2021-12-15 15:21
回复
没有更多了
发现更多内容

【万字长文】前端性能优化实践 | 京东云技术团队

京东科技开发者

性能优化 前端

从BST到LSM的进阶之路 | 京东物流技术团队

京东科技开发者

数据结构 Lsm 数据结构与算法 BST 企业号11月PK榜

探索数据湖和大数据在亚马逊云服务云存储服务上的威力

亚马逊云科技 (Amazon Web Services)

大数据 S3 EMR Amazon Glue 云存储服务

什么是稳定扩散,它是如何工作的?

3D建模设计

机器学习 Stable Diffustion 稳定扩散

Topaz Gigapixel AI 激活密钥文件 Topaz Gigapixel AI破解版下载

影影绰绰一往直前

Typora for Mac中文版(Markdown文本编辑器)「支持m1 」

影影绰绰一往直前

Typora破解 Typora Mac下载

SketchUp Pro 2023 中文for Mac激活版下载(草图大师 2023破解)

影影绰绰一往直前

Apifox 新功能发布:自动化测试迎来重大突破!

Apifox

程序员 测试 自动化测试 Apifox 测试工具

稳定扩散 img2img 的工作原理

3D建模设计

Stable Diffusion 稳定扩散 img2img text2img

什么是稳定扩散中的采样步骤?

3D建模设计

Stable Diffusion 稳定扩散 图像修复 img2img text2img

稳定扩散与潜伏扩散:哪个更好?

3D建模设计

人工智能 机器学习 Stable Diffusion Latent Diffusion

职业倦怠无所畏惧,智慧云为你的工作增添活力

知者如C

CPU vs GPU:谁更适合进行图像处理?

Finovy Cloud

gpu cpu

App Cleaner & Uninstaller激活中文最新版下载

iMac小白

UBC SDK日志级别重复率优化实践

百度Geek说

大数据 日志 sdk 企业号11月PK榜

使用 Stable Diffusion Img2Img 生成、放大、模糊和增强

3D建模设计

Stable Diffusion 稳定扩散 图像修复 AI 图像生成

Stable Diffusion 是否使用 GPU?

3D建模设计

Stable Diffusion 稳定扩散 img2img text2img AI图像合成

企业软件app定制开发流程

Geek_16d138

app定制开发 软件开发定制

KeyShot 2023.3 Pro for mac(3D渲染和动画制作软件) v12.2.0永久激活版

mac

苹果mac Windows软件 KeyShot Pro 动画软件

苹果Mac电脑 AutoCAD 2024 激活破解版下载

影影绰绰一往直前

MYSQL 事务的底层原理 | 京东物流技术团队

京东科技开发者

MySQL 数据库 事务 底层原理 企业号11月PK榜

BusyCal for Mac(任务日历工具) 2023.4.1中文激活版

mac

苹果mac Windows软件 BusyCal 日历应用程序

软件测试/测试开发丨探索标识符在编程中的重要性

测试人

软件测试

IntelliJ IDEA 2023最新激活码(附IntelliJ IDEA 2023破解版下载)

iMac小白

如何通过一个 SQL 语句构建实时数仓?

NineData

数据分析 sql 数据库系统 数据查询 异构

OmniGraffle for mac(绘图软件) v7.22.2中文正式版

mac

苹果mac Windows软件 OmniGraffle 图表设计软件

KeyShot 2023 Pro渲染器激活安装包 KeyShot 破解下载「支持m1 m2」

影影绰绰一往直前

KeyShot Pro下载 KeyShot Pro破解版 KeyShot Pro 2023下载

文件夹图标修改器:Folder Factory for mac激活版

iMac小白

Folder Factory下载 Folder Factory破解版 Folder Factory mac

架构设计原则之我见(二):SOLID原则_架构_王概凯_InfoQ精选文章