单一职责原则(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实现非常简单:
class TransferGameData {
connection: any;
connect() {
}
disconnect() {
}
transfer() {
}
}
明显,这个class有两个职责:连接管理(connect与disconnect)和数据传输(transfer),这样也不是不能用,但问题是将来对连接管理和数据传输有改动时,都会改动这个class,这样引起这个class的改动的原因就有两个。所以最好将这个两个职责分开,可以简单的使用class inhert来拆分这两个职责。
class TransferConnectionManagement {
connection: any;
connect() {
}
disconnect() {
}
}
class TransferGameData extends TransferConnectionManagement {
transfer() {
}
}
更进一步,可以将这两种职责定义为两个interface,使用一个class来实现这两个interface,虽然职责的代码同在一个class中,但对外暴露的其实是两个职责的interface。
interface ITransferConnectionManagement {
connection: any;
connect: () => void
disconnect: () => void
}
interface ITransferGameData {
transfer: () => void
}
class TransferGameData implements ITransferGameData, ITransferConnectionManagement {
connection: any;
connect() {
}
disconnect() {
}
transfer() {
}
}
function transfer(tgd_cls: ITransferGameData & ITransferConnectionManagement) {
tgd_cls.connect();
tgd_cls.transfer();
tgd_cls.disconnect();
}
transfer(new TransferGameData());
这样做有什么好处?显而易见,使用interface后意图更清晰了,在transfer函数中的参数tgd_cls需要被实现为ITransferGameData & ITransferConnectionManagement,我们能够很快的理解tgd_cls需要什么类型的数据、tgd_cls能够干什么,而不是去看class实现。
当然,使用第一种方案也不是不行,SRP并没有绝对的标准,但使用interface后,我的明显感觉是整体代码的可读性提升了,可维护性可能通过这个小例子体现不出来,但相比于transfer函数参数类型为class,使用interface对于可读性是提升了的,使用class作为参数类型总有一种混沌的感觉。
对于Rust来说,trait机制可以方便的实现面向接口的单一职责原则,同时trait可以有默认行为,所以直接将三种功能在trait中进行默认实现即可,相对于ts版本更加清晰。
trait ITransferConnectionManagement {
fn connect(&self) {}
fn disconnect(&self) {}
}
trait ITransferGameData {
fn transfer(&self) {}
}
struct TransferGameData {}
impl ITransferConnectionManagement for TransferGameData {}
impl ITransferGameData for TransferGameData {}
fn transfer(tgd: impl ITransferConnectionManagement + ITransferGameData) {
tgd.connect();
tgd.transfer();
tgd.disconnect();
}
但是在实际开发中,由于各种各样的因素,往往很难实现单一职责原则,所以并不是强制要求所有interface或者class都得做到单一职责,还是因项目而异,但使用了单一职责原则,确实能使代码更加清晰易读。