SRP:单一职责的原则
一、定义。
SRP(Single Responsibility Principle):单一职责的原则:一个类应该只有一个发生变化的原因。
每一个职责都是变化的轴线。当需求变化时,该变化会反应为类的职责变化。如果一个类承担了多于一个的职责,那么引起它变化的原因就会有多个。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会消弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。
如下图的设计。Rectangle类具有两个方法,一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。
有两个不同的应用程序使用Rectangle类,一个应用程序是有关计算几何学方面的,利用Rectangle类计算几何形状,但不会在屏幕上绘制矩形。另外一个应用程序实质上是有关图形绘制方面的,它可能也会进行一些计算几何学方面的工作,但是他肯定会在屏幕上绘制矩形。
这个设计违反了单一职责的原则(SRP)。Rectangle类具有两个职责。
第一:提供了一个矩形几何形状的数学模型;
第二:把矩形在一个图形用户界面上绘制出来。
导致的问题:
首先,我们必须在计算几何应用程序中包含进GUI代码。在.NET中,就必须要把GUI组建和计算几何应用一起构建、部署。
其次,如果GraphicalApplication的改变由于一些原因导致了Rectangle的改变,那么这个改变会迫使我们重新构建、测试以及部署ComputationalGeometryApplication。如果忘记了这样做,ComputationalGeometryApplication可能会有不可预测的问题。
一个较好的设计是把这两个职责分离到下图中所示的两个完全不同的类中。
二、定义职责
在SRP中,我们把职责定义为变化的原因。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。
如下代码中Modem的接口,大都数人都会认为这个接口看起来非常合理。该接口所声明的4个方法确实是调制解调器所具有的功能。
public interface IModem { void Dial(string pno); void Hangup(); void Send(char c); char Recv(); }
然而,该接口中却显示出两个职责。第一:链接管理;第二:数据通信。Dial和Hangup方法进行调制解调器的链接处理,而Send和recv方法进行数据通信。
这两个职责应该分开吗?这依赖于应用程序的变化方式。如果应用程序的变化会影响到链接方法的签名(signature),那么这个设计就具有僵化性的臭味,因为调用Send和Recv的类必须要重新编译、部署的次数常常会超过我们希望的次数。在这种情况下,这两个职责应该被分离,如下图。这样做避免了客户应用程序和这两个职责耦合在一起。
另一方面,如果应用程序的变化方式总是导致这两个职责同时变化,那么就不必分离他们。这时,分离它们就会具有不必要的复杂性的臭味。
仅当变化发生时,变化的轴线(职责)才具有实际的意义。
我们把两个职责都耦合进了ModemImplementation类中,这不是所希望的,但或许是必要的,常常由于一些和硬件或者操作系统的细节有关的原因,迫使我们这样去处理。我们可以把它看做一个有缺陷的类,所有的依赖关系都是从它出发,谁也不需要依赖它。除了Main以为,谁也不需要知道它的存在。因此我们已经把丑陋的部分隐藏起来。