SRP:TheSingle Responsibility Principle
单职原则
None but Buddha himself must take theresponsibility of giving out occult secrets...
— E. Cobham Brewer1810–1897.
Dictionaryof Phrase and Fable. 1898.
(译者注:一开始翻译就遇到这句很难翻译的了,本人的理解为“就连佛祖都有他的职责”)
SRP原则是TomDeMarco和 Meilir Page-Jones在工作的时候提出的。他们称这个原则为“内聚”。在21章,我们说给出更多在的Package层次的定义说明。这个SRP原则是针对Class层次的。
SRP:单职原则
修改一个类的理由不应该超过一个。
以第六章的保龄球游戏为例子。Game这个类拥有2个完全独立的职责。它不停地追踪每一帧,并进行分数计算。最后它被分割为RCM和RSK类。Game类保持其原有追踪帧与计算得分的职责。
为什么将这2个职能分割到2个类中呢?这是因为这2个职能都是以其自身为修改点的。我们只需要修改对应类就可以修改对应的职能。一个类超过一个职能,那么我们将会有多个原因去修改它(译者注:一个职能的修改归结为一个原因)。
例如图9-1的设计。Rectangle类有2个方法。一个是渲染矩形到屏幕,另一个是计算矩形的面积。
超过一个职能
2个不同的程序去使用Rectangle类。一个程序是几何运算程序,需要使用Rectangle类来进行对几何图形进行数学运算,它并不需要将矩形输出到屏幕。另一个程序是图像显示程序,它在需要几何运算的同时也需要将矩形输出到屏幕上。
这个设计违背了SRP。矩形有2个职责,一是计算面积,二是GUI相关的渲染。
违背SRP会导致一些问题。
首先,这样的Rectangle类,需要包含GUI相关代码到几何运算程序中。如果它是一个C++程序,那么就需要link相关的GUI库了,增加了complie、link内存的开销。如果是一个JAVA程序,那么它也需要依赖相关平台的GUI库。
其次,如果图像显示程序,需要修改Rectangle类,那么几何运算程序,如果你忘记了做这一步,那么几何运算程序将会因为函数指针的偏移而导致程序随时崩溃。
一个比较好的设计就是将职能划分,如图9-2。将渲染和面积计算划分到不同的类,这样2个程序就独立开来了。
什么是职能?
上文我们将SRP定义为一个职能职能因为“一个理由而去改变”。如果你有多于一个动机去改变一个类,那么这个类其实就是有多个职能了。很多时候,这个情况是很困难发现的。我们习惯将一个逻辑归到一个职能中。例如下面表格9-1中Modem这个interface。大多数人都认为它是比较合理的。Modem的4个功能分别由4个函数去声明。
然而,其实它隐含了2个职能。dial和hangup函数属于通信管理,send和recv属于数据传输。
这2个职能是否应该分隔呢?在大多数的情况下,他们都应该被分隔。这2组函数几乎没有共同之处。修改他们肯定是出于不同的理由。此外,这2组函数也会被不同的程序模块去调用。这些差异也导致了修改原因的差异。
因此图9-3的设计就比较合理。将其只能分隔到2个interface中。最后客户端程序将2个接口进行连接。
然而,你会发现我又将2个职能归结到ModemImplementation类中了。虽然让人不爽,但这是必须的。我们经常会因为一些原因,如硬件或者操作系统,导致我们将一些东西耦合起来。但是,将他们分隔到不同的interface的话,我们又需要将剩余的部分进行解耦。
我们可以将ModemImplementation类看作是一个组装机。需要注意的是所有的依赖都会从它消失。因为其他所有类都不依赖与它。除了main函数,其他类都没不需要知道它的存在。因此,我们将这些丑陋的实现都放到ModemImplementation里面,而且丑陋的实现又不会污染程序的其他部分。
结论
SRP是一个最简单的原则,但也是最难做得好的。我们很自然地将职责连接起来。所谓软件设计,其实就是寻找职责并将其分隔。剩下我们将要讨论的原则都用这样或那样的方式回到SRP这个点上。