单一职责原则
约 1247 字大约 4 分钟
2022-04-08
定义
单一职责原则 ( SRP ) 。
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
作用
- 一个类承担的职责越多,它被复用的可能性就越小。
- 当一个职责变化时,可能会影响其他职责的运作。
- 将这些职责进行分离,将不同的职责封装在不同的类中。
- 将不同的变化原因封装在不同的类中。
- 单一职责原则是实现高内聚、低耦合的指导方针。
优点
- 组织代码。 - 让我们想象一个汽车修理工。他使用很多工具一起工作。这些工具按类型分为:钳子,螺丝刀,锤子,扳手等,他如何组织管理这些工具呢?他使用许多小抽屉将这些工具分门别类存放,这些抽屉其实类似模块作用,专门用来管理各种类。 
- 减少脆弱。 - 当一个类有多个理由需要修改时,它变得脆弱,在一个地方的修改会导致其他地方不可预期的后果。 
- 更松耦合。 - 更多职责责任导致更高的耦合。耦合也是一种责任,高度耦合导致高度依赖,意味着难以维护。 
- 代码改变。 - 对于单一职责模块重构更容易。如果你想获得猎枪的效果,就让你的类有更多职责。 
- 维护性。 - 维护一个单一职责的类比维护一个铁板一块的类更容易。 
- 易于测试。 - 测试单一目标的类只需要很少的测试类。 
- 易于调试。 - 在一个单一职责类找到问题是一件更容易的事情。 
如何识别 SRP 被破坏?
- 类有太多依赖。 - 类的构造器有太多参数,意味着测试有太多依赖,需要制造 mock 太多测试输入参数,通常意味着已经破坏 SRP 了。 
- 方法有太多参数。 - 类似类的构造器,方法参数意味着依赖。 
- 测试类变得复杂。 - 如果测试有太多变量,意味着这个类有太多职责。 
- 类或方法太长。 - 如果方法太长,意味着内容太多,职责过多。一般一个类不超过 200-250 行。 
- 描述性名称。 - 如果你需要描述你的类,方法或包需要使用"xxx 和 xxx"这种语句,意味着可能破坏了 SRP。 
- 低聚合的类。 - 聚合 ( Cohesion ) 是一个很重要的概念,虽然聚合是有关结构概念,但是聚合和 SRP 非常相关。如前面论坛案例,如果一个类不代表一个高聚合,意味着低凝聚 ( low Cohesion ) ,它就可能意味破坏 SRP。一个低凝聚的特点:一个类有两个字段,其中一个字段被一些方法使用,另外一个字段被其他方法使用。 
- 在一个地方改动影响另外一个地方。 - 如果在一个代码地方加入新功能或只是简单重构,却影响了其他不相关的地方,意味着这个地方代码可能破坏了 SRP。 
- 猎枪效果 Shotgun Effect。 - 如果一个小的改变引起一发动全身,这意味 SRP 被破坏了。 
- 不能够封装模块。 - 比如使用 Spring 框架,你使用 @Configuration 或者 XML 配置,如果你不能在一个配置中封装一个 Bean 意味着它有太多职责。Spring 配置应该隐藏内部 Bean,暴露最少接口。如果你因为多个原因需要改变 Spring 配置,可能破坏了 SRP。 
示例
反例
class Modem {
   void dial ( String pno ) ;    // 拨号
   void hangup ( ) ;            // 挂断
   void send ( char c ) ;        // 发送数据
   char recv ( ) ;              // 接收数据
};乍一看似乎没有什么,但是事实上 Modem 类承担了连接管理和数据管理两部分。而这两个部分实际上是没有关联的。
正例
# ifndef DIALER_H
# define DIALER_H
# include <string>
class Dialer {
public:
    void dial ( const std:: string& phoneNumber ) {
        // 执行拨号操作
    }
    void hangup ( ) {
        // 执行挂断操作
    }
};
# endif# ifndef CONNECTIONMANAGER_H
# define CONNECTIONMANAGER_H
# include "Dialer.h"
class ConnectionManager {
private:
    Dialer dialer;
public:
    void dial ( const std:: string& phoneNumber ) {
        dialer.dial ( phoneNumber ) ;
    }
    void hangup ( ) {
        dialer.hangup ( ) ;
    }
};
# endif# ifndef DATASENDER_H
# define DATASENDER_H
class DataSender {
public:
    void send ( char c ) {
        // 执行发送数据操作
    }
};
# endif# ifndef DATARECEIVER_H
# define DATARECEIVER_H
class DataReceiver {
public:
    char recv ( ) {
        // 执行接收数据操作
        return 'a'; // 假设接收到字符 'a'
    }
};
# endif# ifndef DATATRANSFERMANAGER_H
# define DATATRANSFERMANAGER_H
# include "DataSender.h"
# include "DataReceiver.h"
class DataTransferManager {
private:
    DataSender sender;
    DataReceiver receiver;
public:
    void send ( char c ) {
        sender.send ( c ) ;
    }
    char recv ( ) {
        return receiver.recv ( ) ;
    }
};
# endif总结
该原则的核心在于掌握代码组织的粒度。尽可能地缩小这个粒度,以此增加代码的复用性以及代码变更的影响范围。
贡献者
更新日志
- 941fa-feat(theme): upgrade and use collections于
- eb6eb-improve(docs): use pangu formatter于
- dd170-feat(docs): add solid principles series于
- f29bc-improve(docs): use chinese punctuation于
- 25255-fix(docs): text typo于
- fea7c-improve(docs): delete extra whitespace and blank lines于
- c1c02-modify(docs): remanage folders and rename files于
- d5800-docs: update docs于
- adccc-整理设计原则整个部分于
- 756e1-设计模式改为设计原则于