写点什么

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

评论 1 条评论

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

修复一个kubernetes集群

不在线第一只蜗牛

Kubernetes 容器 云原生

京东商品详情API全攻略:返回值字段一网打尽

技术冰糖葫芦

API 接口 API 文档 API 测试

科技是把双刃剑,巧用技术改变财务预测

智达方通

企业管理 数字化 科技 全面预算管理

可观测日北京|观测云:可观测性需要做到“三个一”

观测云

观测云

微博热搜API:实时数据获取与处理

幂简集成

API 微博

系统数据安全解决方案(医疗行业Word原件)

金陵老街

信息安全 数据安全 数据互联互通

望繁信科技创始人索强出席2022福布斯中国·青年海归菁英100人评选颁奖典礼

望繁信科技

数字化转型 流程挖掘 流程资产 流程智能 望繁信科技

IEPL专线:企业网络的高速保障

Ogcloud

企业组网 企业网络 IEPL 企业网络专线 网络专线

天润融通引领知识库革命,大模型技术实现自动知识采集

天润融通

人工智能

免费报名!第五届“医疗大数据学术交流及 Datathon 活动”诚邀您的参加

ModelWhale

喜报丨时序数据库 IoTDB 荣获“创客北京 2024”创新创业大赛专项赛优胜奖

Apache IoTDB

下一代 AI 陪伴 | 平等关系、长久记忆与情境共享 | 播客《编码人声》

声网

单月30k+ Downloads!一款头部Embedding开源模型

合合技术团队

开源 工具 科技

Claude 大更新,AI 可模仿人类访问电脑;月之暗面招募微软亚研院谭旭,研发类 GPT- 4o 的端到端语音模型

声网

低代码开发:数字化转型,轻松“点”到为止!

不在线第一只蜗牛

低代码

【FAQ】HarmonyOS SDK 闭源开放能力 —IAP Kit(3)

HarmonyOS SDK

HarmonyOS

天润融通推出智能语音导航,自动识别客户意图实现高效分流

天润融通

软件需求分析报告完整版(软件项目套用原件)

金陵老街

软件设计 需求分析 软件需求设计

淘宝详情API接口有什么应用?

科普小能手

API 接口 API 测试 淘宝API接口

天润融通知识库赋能一线客户运营,不是宝妈也可以成为育儿专家

天润融通

第71期 | GPTSecurity周报

云起无垠

ETLCloud搭配MySQL | 让关系型数据库更智能

RestCloud

MySQL 数据库 sql ETL 数据集成

天润融通大模型文本机器人,让客服迈入“无人化”的第一步

天润融通

MatrixOne 助力 StoneCastle 打造高性能金融分析系统

MatrixOrigin

数据库 金融 HTAP

软件测试学习笔记丨Selenium学习笔记:三种等待方式

测试人

软件测试

深入理解 JavaScript 中的剩余参数和扩展运算符

秃头小帅oi

和鲸科技亮相重庆市医学会临床流行病学和循证医学分会 2024 学术年会,探索临床研究标准化新路径

ModelWhale

人工智能 大数据 大模型 临床医学

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