产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

架构设计原则之我见(二):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:172304

评论 1 条评论

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

亚太唯一,阿里云连续4年稳居Gartner全球云数据库报告「领导者」

阿里云瑶池数据库

数据库 阿里云 云原生 Gartner

工作会议:更适合中国宝宝体制的party

伤感汤姆布利柏

PCB阻抗设计12问,轻松带你搞懂阻抗!

华秋电子

奋发向前展华章 秋光熠熠映朝阳 ——华秋2023年度关键词“发展”

华秋电子

垂直领域大模型——文档图像大模型的思考与探索

陈橘又青

人工智能 文档识别 智能文字识别技术

如何快速搭建AI数字人系统源码?

伤感汤姆布利柏

腾讯云大数据ES:结合AI大模型与向量检索的新一代云端检索分析引擎

腾讯云大数据

ES

XSKY SDS 产品率先获得 OceanBase V4 新版本认证

XSKY星辰天合

每个程序员都应该知道的 8 大算法

不在线第一只蜗牛

c++ Python 算法

理解 Node.js 中的事件循环

高端章鱼哥

node.js

软件测试开发/全日制丨测试方案设计 学习笔记

测试人

软件测试 自动化测试 测试开发

Web3 生态系统中的稳定币发展:2024 年基本见解

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

Apache Paimon:Streaming Lakehouse is Coming

Apache Flink

大数据 flink 实时计算

程序员必备的数据库知识 2:Join 算法

快乐非自愿限量之名

Kafka Streams概述

FunTester

SMART Utility for mac (硬盘检测工具) 3.2.7完美激活版

mac

苹果mac Windows软件 SMART Utility 磁盘诊断工具

HTTP 和 HTTPS 的比较

Liam

程序员 前端 https 后端 HTTP

「2023中国ToB行业年度榜单」重磅启动!

ToB行业头条

制造业企业使用SD-WAN的意义

Ogcloud

网络 SD-WAN 组网 组网网络

OP链、ARB链、TRX波场链丨BNB币安链质押挖矿系统开发丨源码搭建

l8l259l3365

FDA 9月27号最新发布!SBOM将作为医疗设备上市的重要参考材料!

安势信息

SBOM 医疗器械 医疗器械软件 FDA认证

软件测试开发/全日制丨redis击穿、穿透区别,如何设计用例及测试

测试人

redis 软件测试 测试开发

码上加速,低代码解锁高效交付案例

京东科技开发者

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