Zhengrenzhe

(°∀°)ノ 老打杂了

用户工具

站点工具


设计模式:原则:单一职责原则

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

设计模式/原则/单一职责原则.txt · 最后更改: 2020/02/26 15:38 (外部编辑)