Z

单一职责原则(Single Responsibility Principle)

There should never be more than one reason for a class to change.

单一职责原则要求一个interface或者class只有一个原因引起变化,也就是说它只有一个职责,负责一件事。按职责拆分可以使需求变动引起的改动最小化,因为只有负责该职责的代码需要改动。

假设要实现一个通过TCP传输数据的class,主要有三个API:

  • connect
  • disconnect
  • transfer

用TypeScript实现非常简单:

 1class TransferGameData {
 2    connection: any;
 3
 4    connect() {
 5    }
 6
 7    disconnect() {
 8    }
 9
10    transfer() {
11    }
12}

明显,这个class有两个职责:连接管理(connect与disconnect)和数据传输(transfer),这样也不是不能用,但问题是将来对连接管理和数据传输有改动时,都会改动这个class,这样引起这个class的改动的原因就有两个。所以最好将这个两个职责分开,可以简单的使用class inhert来拆分这两个职责。

 1class TransferConnectionManagement {
 2    connection: any;
 3
 4    connect() {
 5    }
 6
 7    disconnect() {
 8    }
 9}
10
11class TransferGameData extends TransferConnectionManagement {
12    transfer() {
13    }
14}

更进一步,可以将这两种职责定义为两个interface,使用一个class来实现这两个interface,虽然职责的代码同在一个class中,但对外暴露的其实是两个职责的interface。

 1interface ITransferConnectionManagement {
 2    connection: any;
 3    connect: () => void
 4    disconnect: () => void
 5}
 6
 7interface ITransferGameData {
 8    transfer: () => void
 9}
10
11class TransferGameData implements ITransferGameData, ITransferConnectionManagement {
12    connection: any;
13
14    connect() {
15    }
16
17    disconnect() {
18    }
19
20    transfer() {
21    }
22}
23
24function transfer(tgd_cls: ITransferGameData & ITransferConnectionManagement) {
25    tgd_cls.connect();
26    tgd_cls.transfer();
27    tgd_cls.disconnect();
28}
29
30transfer(new TransferGameData());

这样做有什么好处?显而易见,使用interface后意图更清晰了,在transfer函数中的参数tgd_cls需要被实现为ITransferGameData & ITransferConnectionManagement,我们能够很快的理解tgd_cls需要什么类型的数据、tgd_cls能够干什么,而不是去看class实现。

当然,使用第一种方案也不是不行,SRP并没有绝对的标准,但使用interface后,我的明显感觉是整体代码的可读性提升了,可维护性可能通过这个小例子体现不出来,但相比于transfer函数参数类型为class,使用interface对于可读性是提升了的,使用class作为参数类型总有一种混沌的感觉。

对于Rust来说,trait机制可以方便的实现面向接口的单一职责原则,同时trait可以有默认行为,所以直接将三种功能在trait中进行默认实现即可,相对于ts版本更加清晰。

 1trait ITransferConnectionManagement {
 2    fn connect(&self) {}
 3    fn disconnect(&self) {}
 4}
 5
 6trait ITransferGameData {
 7    fn transfer(&self) {}
 8}
 9
10struct TransferGameData {}
11
12impl ITransferConnectionManagement for TransferGameData {}
13
14impl ITransferGameData for TransferGameData {}
15
16fn transfer(tgd: impl ITransferConnectionManagement + ITransferGameData) {
17    tgd.connect();
18    tgd.transfer();
19    tgd.disconnect();
20}

但是在实际开发中,由于各种各样的因素,往往很难实现单一职责原则,所以并不是强制要求所有interface或者class都得做到单一职责,还是因项目而异,但使用了单一职责原则,确实能使代码更加清晰易读。