【设计原则篇】单一职责原则(SRP)

1、单一职责原则(single responsibility principle):

就一个类而言,应该仅有一个引起它变化的原因。

  如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的(fragile)设计,当变化发生时,设计会遭受到意想不到的破坏。

例如,考虑图1中的设计,Rectangle类具有两个方法,如图所示。一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。

image

图1 多于一个的职责

  有两个不同的应用程序使用Rectangle类,一个是有关计算机几何学方面的,Rectangle类会在几何形状计算方面为它提供帮助,它从来不会在屏幕上绘制矩形。另外一个应用程序实质上是有关图形绘制方面的,它可能也会进行一些计算几何学方面的工作,但是它肯定会在屏幕上绘制矩形。

  这个设计违反了单一职责原则(SRP),Rectangle类具有两个职责。第一个职责提供了一个矩形几何形状的数学模型;第二个职责是把矩形在一个图形用户界面上绘制出来。

  对于SRP的违反导致了一些严重的问题。首先,我们必须在计算几何应用程序中包含进GUI代码。如果这是一个C++应用程序,就必须要把GUI代码链接进来,这会浪费链接时间、编译时间以及内存占用。如果是一个Java应用程序,GUI的class文件要被部署到目标平台。

  其次,如果GraphicalApplication的改变由于一些原因导致了Rectangle的改变,那么这个改变会迫使我们重新构建、测试以及部署ComputationalGeometryApplication。如果忘记了这样做,ComputationalGeometryApplication可能会以不可预测的方式失败。

  一个较好的设计是把这两个职责分离到图2中的两个完全不同的类中。这个设计把Rectangle类中进行计算的部分移到GeometryRectangle类中。现在矩形绘制方式的改变不会对ComputationalGeometryApplication造成影响。

image

图2 分离的职责

2、什么是职责?

  在SRP中,我们把职责定义为"变化的原因"(a reason for change)。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。有时我们很难注意到这一点。我们习惯于以组的方式去考虑职责。例如,考虑下图中的Modem接口。大多数人会认为这个接口看起来非常合理。该接口所声明的4个函数确实是调制解调器所具有的功能。

public interface Modem
{
  // 拨号
  public void dial(string pno);
  // 挂断
  public void hangup();
  // 发送
  public void send(char c);
  // 接收
  public void recv();
}

  然而,该接口中却显示出两个职责。第一个职责是连接管理;第二个职责是数据通信。dial和hangup函数进行调制解调器的连接处理。而send和recv函数进行数据通信。

  这两个职责应该被分开吗?这依赖于应用程序变化的方式。如果应用程序的变化会影响连接函数的签名(signature),那么这个设计就具有僵化性的臭味,因为调用send和recv的类必须要重新编译,部署的次数常常会超过我们希望的次数。在这种情况下,这两个职责应该被分离。如图3所示,这样做避免类客户应用程序和这两个职责耦合在一起。

image

图3 分离的Modem接口

  另一方面,如果应用程序的变化方式总是导致这两个职责同时变化。那么就不必分离它们。实际上,分离它们就会具有不必要的复杂性的臭味。

  在此还有一个推论。变化的轴线仅当变化实际发生时才具有真正的意义。如果没有征兆,那么去应用SRP,或者任何其他原则都是不明智的。

3、分离耦合的职责

  请注意,在图3中,我把两个职责都耦合进了ModemImplementation类中。这不是所希望的,但是或许是必要的。常常会有一些和硬件或者操作系统的细节有关的原因,迫使我们把不愿耦合在一起的东西耦合在了一起。然而,对于应用的其余部分来说,通过分离它们的接口我们已经解耦了概念。

  我们可以把ModemImplementation类看作是一个杂凑物(kludge),或者一个瑕疵。然而请注意所有的依赖关系都和它无关。谁也不需要依赖于它。除了main外,谁也不需要知道它的存在。

4、持久化

  图4展示类一种常见的违反SRP的情形。Employee类包含了业务规则和对于持久化的控制。这两个职责在大多数情况下决不应该混合在一起。业务规则往往会频繁的变化,而持久化的方式却不会如此频繁的变化,并且变化的原因也是完全不同的。把业务规则和持久化子系统绑定在一起的做法是自讨苦吃。

  幸运的是,测试驱动的开发实践常常会远在设计出现臭味之前就迫使我们分离这两个职责。然而,在测试不能迫使职责分离的情况下,僵化性和脆弱性的臭味会变得很强烈,那么就应该使用FACADE(门面模式/外观模式)或者PROXY(代理)模式对设计进行重构,分离这两个职责。

image

图4 被耦合在一起的持久化职责

5、结论

SRP是所有原则中最简单的之一,也是最难正确运行的之一。我们会自然地把职责结合在一起。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。事实上,我们将要论述的其余原则都会以这样或者那样的方式回到这个问题上。

References:

《Agile Software Development Principles,Patterns,and Practices》(Robert C.Martin)
posted @   Harley-Chang  阅读(400)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示