QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

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

评论 1 条评论

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

跨国企业如何选择跨境数据传输平台,了解这篇就够了

镭速

跨境数据传输

和鲸赞助!第16届中国R会议暨2023 X-AGI大会通知

ModelWhale

机器学习 R 数据科学 X-AGI 统计之都

实测亚马逊 AI 编程助手 Amazon CodeWhisperer

全栈若城

支付宝权限问题大全|一文搞定,持续更新

盐焗代码虾

支付宝 经验分享 支付宝权限

唱衰PHP?这些言论别太离谱~《PHP综合现状分析报告》来了

禅道项目管理

php

ETL工具与数据处理的关系

RestCloud

数据处理 ETL

用友BIP智能分析资产包正式发布,助力企业智能化决策!

用友BIP

智能分析

Mac电脑图像处理AI软件 Topaz Photo AI最新激活版

mac大玩家j

Mac软件 图像处理软件 图像编辑处理

云图说|华为云CodeArts Build,云端化的编译构建平台

华为云开发者联盟

华为云 华为云开发者联盟 编译构建

一文了解企业云盘和大文件传输哪个更适合企业传输

镭速

大文件传输

2023-10-25:用go语言,假如某公司目前推出了N个在售的金融产品(1<=N<=100) 对于张三,用ai表示他购买了ai(0<=ai<=10^4)份额的第i个产品(1<=i<=N) 现给出K(

福大大架构师每日一题

福大大架构师每日一题

TuGraph Analytics图建模研发:为图计算业务提速增效

TuGraphAnalytics

分布式 图计算 图平台 图研发 图运维

用友全球司库十问(五)| 企业如何主动管理资金流动性?

用友BIP

全球司库 流动性管理

装备修理行业数智化转型之道

用友BIP

装备修理行业

介绍SDK(C++版)使用方法

矩视智能

深度学习 机器视觉

语雀服务宕机带来的稳定性思考

老张

高可用 SRE 稳定性保障

SpringBoot自动配置原理解析 | 京东物流技术团队

京东科技开发者

spring-boot 自动配置 企业号10月PK榜

语音识别技术在医疗健康领域的应用与挑战

来自四九城儿

英特尔以创新边缘AI技术赋能未来智慧城市建设

E科讯

2023 CCF中国开源大会「开放科学——引领未来科学研究发展的新范式分论坛」成功举办

开放原子开源基金会

用友BIP,为打造世界一流光伏数智灯塔工厂聚势蓄能

用友BIP

光伏产业

ConceptDraw PROJECT for Mac(项目管理软件) 14.0.0.302完整激活版

mac

项目管理工具 苹果mac Windows软件 ConceptDraw PROJECT

实况窗功能上线,即时掌握活动最新进展

HarmonyOS SDK

HarmonyOS

开放原子开源基金会联合主办的2023 CCF中国开源大会正式开幕

开放原子开源基金会

开源 CCF

Ready to go: IPQ4029/IPQ4019 with IPQ8072- Connect more and enjoy faster

wifi6-yiyi

wifi IPQ4019 IPQ8072 ipq4029

开放原子开源基金会理事长孙文龙一行赴拓维信息参观交流

开放原子开源基金会

开源

HarmonyOS SDK,赋能开发者实现更具象、个性化开发诉求

HarmonyOS SDK

鸿蒙 HarmonyOS

数智化浪潮中,广电行业收入管理流程该如何重构?

用友BIP

广电行业

1024 特别企划|揭秘 StarRocks 社区背后的神秘力量(内涵福利)

StarRocks

开源社区 StarRocks

万能音视频转换器Permute 3最新中文版

胖墩儿不胖y

Mac软件 音频处理工具 音频格式转换器

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