单一职责原则(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都得做到单一职责,还是因项目而异,但使用了单一职责原则,确实能使代码更加清晰易读。