2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

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

评论 1 条评论

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

空间Web标准:重塑设备交互的未来

qife122

人工智能 空间Web

Interspeech 2025「语音无障碍项目」挑战赛落幕

算AI

人工智能 算法 语音 ASR

超实用!一篇文章讲透分布式锁,建议收藏!

王磊

YashanDB TO_BASE64函数

YashanDB

给「AI+软件工程」泼一瓢冷水

行云创新

氛围编程 AI + 软件工程 AI将取代程序员

程序员:氛围编程爽啊!老板:我睡不着哇……

行云创新

平台工程 AI 编码 AI 编码隐患

深度分析前端优化工具Performance面板!

OpenTiny社区

开源 性能优化 前端

YashanDB TIMESTAMP_TO_SCN函数

YashanDB

YashanDB TO_CHAR函数

YashanDB

数智先锋 | Bonree ONE 赋能通威股份有限公司提升全栈可观测性能力

博睿数据

如何实现 AI Agent 自主发现和使用 MCP 服务 —— Nacos MCP Router 部署最佳实践

阿里巴巴云原生

阿里云 云原生 nacos MCP

AI赋能舆情监测:从量变到质变的智能跃升

沃观Wovision

人工智能 沃观Wovision 海外舆情监测

文化误读与意见倒灌:品牌如何用全球舆情监测防范海外舆情危机?

沃观Wovision

出海企业 海外舆情监控 沃观Wovision 舆情监测系统

Amazon Bedrock的两年征程:从大放厥词到战略实现

Alter

JSON 日志分析的“正确姿势”:阿里云 SLS 高效实践指南

阿里巴巴云原生

json 阿里云 云原生

Function AI 助力用户自主开发 MCP 服务,一键上云高效部署

阿里巴巴云原生

阿里云 云原生 MCP

“75%政府爆发RPA革命!‘数字员工’重塑政务,国产厂商强势突围

Techinsight

预算有限也能高效运维?ManageEngine卓豪高性价比解决方案

ServiceDesk_Plus

ManageEngine卓豪

阿里云可观测 2025 年 7 月产品动态

阿里巴巴云原生

阿里云 云原生 可观测

Splunk Enterprise 10.0.0 (macOS, Linux, Windows) - 搜索、分析和可视化,数据全面洞察平台

sysin

Splunk Enterprise

都说 AI 能给研发开外挂,可企业为啥总玩不转?答案来了!

行云创新

软件工程 氛围编程 vibe coding

2025年空气净化器品牌选购避坑指南:除甲醛核心技术解析

编程猫

YashanDB TIMESTAMPDIFF函数

YashanDB

送票!2025云栖大会9月24-26日杭州见

阿里巴巴云原生

阿里云 云原生 云栖大会

Bonree ONE发布直通车 | 可观测平台如何深度应用LLM技术

博睿数据

HCL AppScan Standard 10.9.0 新增功能简介

sysin

AppScan

EventLog Analyzer | 助力企业完成等保2.0的重要日志管理工具

运维有小邓

日志分析 日志审计 等保2.0

YashanDB TO_DATE函数

YashanDB

政务RPA如何‘好用’又可控?

Techinsight

图神经网络与AI公平性研究进展

qife122

图神经网络 知识发现

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