一个类应该只有一个发生变化的缘由。测试
由于每个职责都是变化的一个轴线。当需求变化时,这种变化就会反映为类的职责的变化。若是一个类承担了多于一个的职责,那么引发它变化的缘由就会有多个。spa
若是一个类承担的职责过多,就等于把这些职责耦合在了一块儿。一个职责的变化可能会消弱或抑制这个类完成其余职责的能力。这种耦合会致使脆弱的设计,当变化发生时,设计会遭到意想不到的破坏。操作系统
考虑图中的设计。设计
Rectangle类具备两个方法,一个方法把矩形绘制到屏幕上,另外一个方法则是计算矩形的面积。 如今有两个应用程序使用Rectangle类。一个是利用Rectangle计算矩形面积,但不须要绘制矩形;另外一个应用程序可能会绘制矩形,也可能计算面积。3d
这个设计违反了单一职责原则。Rectangle类具备两个职责。第一个职责提供了矩形面积的计算;第二个职责提供了矩形绘制。 对于SRC的违反致使了一些严重的问题。代理
对于这个违反了SRC的程序有如下几点问题:code
首先,咱们必须在左侧的程序中包含进GUI代码,即便咱们是不须要的。blog
其次,若是右侧程序的改变致使了Rectangle的改变,那么这个改变会迫使咱们从新构建、测试和部署左侧的程序。接口
一个较好的设计就是把这两个职责分离到以下图所示的两个彻底不一样的类中。这个设计把Rectangle类中进行计算的部分移到了GeometricRectangle类中。如今矩形绘制方法的变化不会影响到左侧的程序。开发
在SRC中,咱们把职责定义为变化的缘由。
若是你可以想到多于一个的动机去改变一个类,那么这个类就具备多于一个的职责。
有时,咱们很难注意到这一点,咱们习惯于以组的形式去考虑职责。 例如,下面的Modem接口,大多数人认为这个接口很是合理。该接口所声明的4个方法确实是调制解调器所具备的功能 。
public interface Modem { public void Dial(string pno); //链接 public void Hangup(); //断开 public void Send(char c); //发送 public char Recv(); //接收 }
然而,该接口中却显示出两个职责。第一个职责是链接管理;第二个职责是数据通讯。
这两个职责须要分开吗?
这依赖于应用程序变化的方式。若是应用程序的变化会影响到链接管理方法的签名,那么这个设计就具备僵化性,由于调用send和recv的类必须从新编译、部署。在这种状况下,这两个职责应该被分离,以下图所示。
另外一方面,若是应用程序的变化方式老是致使这两个职责同时变化,那就没必要分离它们。
记住这么一个结论,仅当变化发生时,变化的轴线才具备实际意义。若是没有征兆,那么应用SRP或者任何其它原则都是不明智的。
请注意,在上图中,把两个职责都耦合进了ModemImplementation类中。这也许不是最好的,可是或许必须得这么作。经常会有一些和硬件或者操做系统的细节有关的缘由,迫使咱们把不肯意耦合在一块儿的东西耦合在了一块儿。然而,对于应用的其他部分来讲,经过分离它们的接口咱们已经解耦了。
下图展现了一种常见的违反SRP的情形。
Employee类包含了业务规则和对于持久化的控制。这个两个职责在大多数状况下毫不应该混合在一块儿。业务规则每每会频繁地变化,而持久化的方式却不会如此频繁的变化,而且变化的缘由也是彻底不一样的,它们实在两个方向上变化,一个是业务方向上变化,另外一个是持久化方向上变化。
把业务规则和持久化子系统绑定在一块儿的作法是自讨苦吃。 测试驱动的开发实践经常会远在设计出现臭味以前就迫使咱们分离这两个职责。然而,若是测试没有迫使这种分离,那么就应该使用Facade(外观)、DAO(数据访问)或者Proxy(代理)模式对设计进行重构,分离这两个职责。
SRP是全部原则中最简单的原则之一,也是最难正确运用的原则之一。咱们会不自觉地把职责结合到一块儿。软件设计真正要作的许多工做,就是发现职责并把那些职责相互分离。