单一功能原则 Single Responsibility Principle

关於物件导向程序设计的五个设计原则,大家可能会依据不同的顺序来解释,不过我想「单一功能原则」一定会被摆在首位。

这个原则由 "Uncle Bob" (Robert C. Martin) 於 2003 年在他的着作 Principles of Object Oriented Design 当中所提出,其描述相当简短:

A class should have only one reason to change

每一个类别只会因为一种原因被修改。从另外一个角度来说,就是每一个类别只会有一种功能,只有当依赖这个功能的需求变动的时,才需要修改这个类别。

如果回想设计原则存在的原因:面对需求快速变动的世界,那麽这个原则所要告诉我们的就是,尽可能将功能与职责拆分,一种类别就单纯处理一种功能、需求,因此当未来需求变动的时候,我们只要修改负责的类别就行,降低大规模修改程序码、甚至影响到其他功能的状况。

关於单一功能

那好吧,我们就开始拆分各种功能,拆分到每一个类别里面只有一种属性或方法,那这样就非常「单一」了!

然而这样反而就违背了物件导向程序设计本身的意义:透过物件来模拟真实世界。

举例来说,如果今天我们希望透过物件来描述一位棒球选手,不过因为单一功能原则,所以我们需要「大量」的继承各种类别、实作各种介面,才能好好描述一位棒球选手,似乎就本末倒置,无法容易的描述真实世界的事物。

另一方面,这个棒球选手类别,其实就高度依赖其他类别与介面,如果其他类别或介面因为其他需求变动而修改,反而棒球选手类别也就会跟着受到影响。

所以,到底什麽是「单一功能」呢?其实 Uncle Bob 後来有补充了一句话:

This principle is about people.

回过头来,这个原则是「关於人们」,但这是什麽意思呢?

关於人们

当我们在设计任何程序的时候,最终目的都是要来解决人们在真实世界当中遇到的问题,所以「单一功能」的角度,并不是指程序本身功能的角度,而是人们的需求功能(或业务功能)的角度。

也就是说,我们会希望一个类别不是只处理一种程序功能,而是能够处理某一种人们(业务)需求,而当这个需求变动的时候,我们只要回头修改这个类别就行,不影响到其他类别。

但这时候又有另外一个问题,要怎麽定义或规范业务需求的范围呢?譬如以一间咖啡店来说,一开始我们可以建立一个超大类别,涵盖经营咖啡店所需要的各种属性和方法,接着,我们可以开始拆分业务,建立吧台人员、柜台人员、後勤物流人员 ... 等类别,来分别处理各种需求。

但是如果咖啡店的经营规模持续扩大,吧台人员可能又可以继续拆分成负责饮料的吧台人员、负责甜点的吧台人员、负责热食的吧台人员 ... 等。

最後会发现,现实世界当中的业务,也可以像程序码的功能一样,不断的拆分下去。那麽这样,要怎麽决定「单一功能」所需要面对的业务范围呢?

关於范围

再次回到设计原则存在的原因:面对需求快速变动的世界,以刚刚的咖啡店例子来说,如果吧台人员所面对的需求并不会变动,那麽其实我们就不需要将吧台人员这个类别拆分的更细,因为原本的这个类别就能够很好的处理业务。

如果这家咖啡店的强项是甜点,每周都会有新品上市,因此需要训练员工准备、介绍新的甜点。所以每当有新甜点出来之後,我们就需要去修改「吧台人员这个类别当中关於甜点的部分」,但每次修改可能就会影响到其他部分(饮料、热食)等,所以这时候我们就可以把「吧台人员这个类别当中关於甜点的部分」给拆分出来,成为独立的类别,专门面对某个快速变动的需求。

小结

所以如果要实现「单一功能原则」,其实背後在做的是拆分真实世界的业务,特别是针对会快速变动的需求,将其拆分出来并建立专门的类别来处理。某个角度来说就是「隔离变化」。

真实世界的业务是不断的在变动,因此实作「单一功能原则」时的细节也会不断的跟着变动。


<<:  Day14

>>:  [ 卡卡 DAY 11 ] - React Native UI 元件(component) 介绍(上)

使用MLFlow tracking功能比较training结果

在上一篇我们已经完成MLFlow的安装, 这篇我们就来说明如何在jupyter notebook里整...

Day 09 - 那个很常用到的 useState

如果有错误,欢迎留言指教~ Q_Q useState:让 component 拥有内部的 stat...

Day 14: 【架构篇】 设计与架构、软件的两大价值 (待改进中... )

「软件架构的目标是最小化 『建置和维护系统所需的人力』」 「架构的规则都是一样的! 年轻设计师可能...

那些被忽略但很好用的 Web API / MutationObserver

我的改变,你看得见! 在开发网页过程中,我们最常做的事情就是对资料进行修改後运用在 DOM 元素上...

[Day23] Flutter GetX with Dio (二)

承袭前一篇Dio 这篇是接着发起request 这次范例是使用News API response 回...