单一职责原则 (Single Responsibility Principle, SRP)
定义:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
单一职责原则是实现 高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
以打电话为例,电话通话的时候有 4 个过程发生:拨号、通话、回应、挂机。那我们写一个接口,类图如下:

代码如下:
/**
上面的的类图对应的接口入下
*/
public interface IPhone{//拨通电话public void dial(String phoneNumber);//通话public void chat(Object o);//挂断电话public void hangup();
}
在看到这个接口的时候,我们都会认为这样的设计是没有问题的,拨通电话,通话,挂断电话写在同一个接口里面并没有什么错。但是,我们仔细分析,这个接口真的没有问题吗?单一职责原则要求一个接口或类只有一个原因引起变化,也就是说一个接口或一个类只有一个原则,它就只负责一件事。 但我们分析上面这个接口,却发现它包含了两个职责: 一个时协议管理, 一个是数据传送。dial()和hangup()两个方法实现的是协议管理,分别是拨通电话和挂机。chat()实现的是数据传送,把我们说的话转换成模拟信号或数字信号传递给对方,然后再把对方传递过来的信号还原成我们听得懂的语言。这里的协议接通和数据传送的变化都会引起该接口或实现类的变化。我们想一想,这两个职责会相互影响吗?不管是什么协议,协议接通只负责将电话接通就行,而数据传输只需要传输数据,不必要去管协议是如何接通的。所以通过分析,IPhone接口包含了两个职责,而且这两个职责的变化不互相影响,这就可以考虑分成两个接口。
笼统地讲:是否需要拆分取决于变化:
当变化发生,只影响其中一个职责,那就需要拆分
如果变化都影响到这两个职责,那就不需要拆分。
若要让IPhone满足单一职责原则,我们就要对其进行拆分,拆分后的类图如下:

这样设计就完美了,一个类实现了两个接口,把两个职责融合在一个类中。你会觉得这个IPhone有两个原因引起变化了啊,是的,但是别忘了我们是面向接口编程,我们对外公布的是接口而不是实现类。
SRP也适用于方法:
其实,单一职责原则不仅适用于类,接口,同样适用于方法中。这要举一个例子了,比如我们做项目的时候会遇到修改用户信息这样的功能模块,我们一般的想法是将用户的所有数据都接收过来,比如用户名,信息,密码,家庭地址等等,然后统一封装到一个User对象中提交到数据库,我们一般都是这么干的,就如下面这样:

其实这样的方法是不可取的,因为职责不明确,方法不明确,你到底是要修改密码,还是修改用户名,还是修改地址,还是都要修改?这样职责不明确的话在与其他项目成员沟通的时候会产生很多麻烦,正确的设计如下:

- 职责的粒度:职责的划分需结合业务场景,避免过度拆分导致类爆炸(如将 “用户登录” 拆分为 “输入账号”“输入密码”“验证” 三个类)。
- 接口与类的区别:接口的单一职责更强调方法的内聚性,类的单一职责需涵盖数据与行为的统一。
案例代码:文件操作类拆分
// 违反SRP:文件操作类同时处理读取和写入
class FileHandler {public String readFile(String path) { /* 读取逻辑 */ }public void writeFile(String path, String content) { /* 写入逻辑 */ }
}// 遵守SRP:拆分为读取类和写入类
interface FileReader {String read(String path);
}interface FileWriter {void write(String path, String content);
}class TextFileReader implements FileReader {@Overridepublic String read(String path) { /* 实现读取 */ }
}class TextFileWriter implements FileWriter {@Overridepublic void write(String path, String content) { /* 实现写入 */ }
}
单一职责的好处:
- 类的复杂性降低,实现什么职责都有清晰明确的定义;
- 可读性高,复杂性降低,可读性自然就提高了;
- 可维护性提高,可读性提高了,那自然更容易维护了;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。