单一职责原则
约 1247 字大约 4 分钟
2022-04-08
定义
单一职责原则(SRP)。
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
作用
- 一个类承担的职责越多,它被复用的可能性就越小。
- 当一个职责变化时,可能会影响其他职责的运作。
- 将这些职责进行分离,将不同的职责封装在不同的类中。
- 将不同的变化原因封装在不同的类中。
- 单一职责原则是实现高内聚、低耦合的指导方针。
优点
组织代码。 让我们想象一个汽车修理工。他使用很多工具一起工作。这些工具按类型分为:钳子,螺丝刀,锤子,扳手等,他如何组织管理这些工具呢?他使用许多小抽屉将这些工具分门别类存放,这些抽屉其实类似模块作用,专门用来管理各种类。
减少脆弱。 当一个类有多个理由需要修改时,它变得脆弱,在一个地方的修改会导致其他地方不可预期的后果。
更松耦合。 更多职责责任导致更高的耦合。耦合也是一种责任,高度耦合导致高度依赖,意味着难以维护。
代码改变。 对于单一职责模块重构更容易。如果你想获得猎枪的效果,就让你的类有更多职责。
维护性。 维护一个单一职责的类比维护一个铁板一块的类更容易。
易于测试。 测试单一目标的类只需要很少的测试类。
易于调试。 在一个单一职责类找到问题是一件更容易的事情。
如何识别 SRP 被破坏?
类有太多依赖。 类的构造器有太多参数,意味着测试有太多依赖,需要制造 mock 太多测试输入参数,通常意味着已经破坏 SRP 了。
方法有太多参数。 类似类的构造器,方法参数意味着依赖。
测试类变得复杂。 如果测试有太多变量,意味着这个类有太多职责。
类或方法太长。 如果方法太长,意味着内容太多,职责过多。一般一个类不超过 200-250 行。
描述性名称。 如果你需要描述你的类,方法或包需要使用"xxx 和 xxx"这种语句,意味着可能破坏了 SRP。
低聚合 Cohesion 的类。 聚合 Cohesion 是一个很重要的概念,虽然聚合是有关结构概念,但是聚合和 SRP 非常相关。如前面论坛案例,如果一个类不代表一个高聚合,意味着低凝聚 low Cohesion,它就可能意味破坏 SRP。一个低凝聚的特点:一个类有两个字段,其中一个字段被一些方法使用,另外一个字段被其他方法使用。
在一个地方改动影响另外一个地方。 如果在一个代码地方加入新功能或只是简单重构,却影响了其他不相关的地方,意味着这个地方代码可能破坏了SRP。
猎枪效果Shotgun Effect。 如果一个小的改变引起一发动全身,这意味 SRP 被破坏了。
不能够封装模块。 比如使用 Spring 框架,你使用 @Configuration or 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
总结
该原则的核心在于掌握代码组织的粒度。尽可能地缩小这个粒度,以此增加代码的复用性以及代码变更的影响范围。
贡献者
更新日志
79076
-improve(docs): use chinese punctuation于42218
-fix(docs): text typo于1289a
-improve(docs): delete extra whitespace and blank lines于c2111
-modify(docs): remanage folders and rename files于aae8b
-docs: update docs于20068
-整理设计原则整个部分于f5cf9
-设计模式改为设计原则于f86ee
-update于93933
-新增文字+CRLF全部替换为LF于fb196
-升级版本+规整文档中的格式于