软件修养 -- 单一职责原则(SRP:Single responsibility principle)

定义

单一职责原则(SRP:Single responsibility principle):一个类应该只有一个发生变化的原因,即一个类只负责一项职责

如果一个类有多个职责,这些职责就耦合在了一起。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起会影响复用性。

此原则的核心是解耦增强内聚性

由来

类 A 负责两个职责:职责 P1,职责 P2。当由于职责 P1 需求发生改变而需要修改类 A 时,有可能会导致原本运行正常的职责 P2 功能发生故障。

解决方案

遵循 SRP。分别建立两个类 A1、A2,使 A1 完成职责 P1,A2 完成职责 P2。这样,当修改类 A1 时,不会影响到职责 A2;同理,当修改 A2 时,也不会影响到职责 P1。

优点

  • 降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多。
  • 提高类的可读性,提高系统的可维护性。
  • 变更引起的风险降低,变更是必然的,如果 SRP 遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

案例

用一个类描述程序员写代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Programmer {
  program(name: String) {
    console.log(name+"写 Typescript 代码");
  }
}

// Client
const programmer = new Programmer();
programmer.program("前端工程师");


//Result
前端工程师写 Typescript 代码

殊不知,Typescript 只是代码界的一部分

1
2
3
4
programmer.program("iOS 工程师");

//Result
iOS 工程师写 Typescript 代码

发现不对劲了,这个时候想到了 SRP,要不这样改改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class WebProgrammer {
  program() {
    console.log("前端工程师写 Typescript 代码");
  }
}

class IOSProgrammer {
  program() {
    console.log("iOS 工程师写 Swift 代码");
  }
}

// Client
const webProgrammer = new WebProgrammer();
webProgrammer.program();

const iOSProgrammer = new IOSProgrammer();
iOSProgrammer.program();

//Result
前端工程师写 Typescript 代码
iOS 工程师写 Swift 代码

我们会发现如果这样修改花销是很大的,除了分解原来的类之外,还需要修改客户端。而直接修改类 Programmer 来达成目的虽然违背了 SRP 但花销却小的多,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Programmer {
  program(name: String) {
    var code = name == "前端工程师" ? "Typescript" : "Swift"
    console.log(name + "写"+ code +"代码");
  }
}

// Client
const webProgrammer = new Programmer();
webProgrammer.program("前端工程师");

const iOSProgrammer = new Programmer();
iOSProgrammer.program("iOS 工程师");

//Result
前端工程师写 Typescript 代码
iOS 工程师写 Swift 代码

可以看到,这种修改方式要简单的多。但是却存在着隐患:有一天需要后台程序员写 PHP,则又需要修改 Programmer 类的 program 方法,而对原有代码的修改会对调用 iOS 工程师、前端工程师带来风险开闭原则)。这种修改方式直接在 代码级别上 违背了 SRP,虽然修改起来最简单,但隐患却是最大。 那么还有别的方式吗?答案是肯定的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Programmer {
  program(name: String) {
    console.log(name + "写 Typescript 代码");
  }

  programSwift(name: String) {
    console.log(name + "写 Swift 代码");
  }
}

// Client
const webProgrammer = new Programmer();
webProgrammer.program("前端工程师");

const iOSProgrammer = new Programmer();
iOSProgrammer.programSwift("iOS 工程师");

//Result
前端工程师写 Typescript 代码
iOS 工程师写 Swift 代码

这种在类中新加一个方法的修改方式,虽然也违背了 SRP,但在方法级别上却是符合 SRP 的,因为它并没有动原来方法的代码

这三种方式各有优缺点,在开发中,需要根据实际情况来确定。需要注意的是:只有逻辑足够简单,才可以在 代码级别上 违反 SRP;只有类中方法数量足够少,才可以在 方法级别上 违反 SRP;

很多人对 SRP 不屑一顾,因为它太简单了。但即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。其原因是因为有职责扩散。所谓职责扩散,就是因为某种原因,职责 P 被分化为粒度更细的职责 P1 和 P2。需要注意的是:在职责扩散到我们无法控制的程度之前,要立刻对代码进行重构

延伸阅读


CatchZeng
Written by CatchZeng Follow
AI (Machine Learning) and DevOps enthusiast.