前言
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)
单一职责原则是 OOP 的一个基本原则,它指出一个类应该只有一个职责。这有助于保持代码可维护、灵活且易于理解。通过应用 SRP,可以创建集中且简洁的类,这使它们更易于理解和重用。
单一职责原则的重要性
遵守单一职责原则是软件设计和开发中的重要原则之一。以下是一些理由解释为什么要遵守单一职责原则:
高内聚性:单一职责原则能够确保类或模块只关注一个特定的功能或任务,从而提高代码的内聚性。类的职责越单一,代码的逻辑结构越清晰、简洁,使得代码容易理解和维护。
可复用性:遵守单一职责原则可以使独立的功能块更容易被复用,在其他系统中使用。由于每个类或模块只负责一个特定职责,因此可以更方便地将其提取出来,与其他部分进行组合使用。
可测试性:当一个类或模块只有一个职责时,测试该职责变得更加简单明确。这样可以更容易编写单元测试,验证类或模块是否正确地执行其职责。
降低耦合性:单一职责原则有助于减少类或模块之间的耦合。当每个类都只负责一个职责时,修改其中一个类不会影响到其他类的功能,从而降低了系统的耦合度,使得代码更加灵活和可扩展。
提高可维护性:遵守单一职责原则使得代码的变动范围更加可控。当需要修改或扩展某个功能时,只需要关注与该功能相关的类,而不会对其他部分产生影响。这简化了维护工作,减少了引入错误和破坏其他功能的风险。
单一职责的应用范围
单一职责原则适用的范围有接口、方法、类。按大家的说法,接口和方法必须保证单一职责,类就不必保证,只要符合业务就行。
【方法层面】
不遵循单一职责的方法
/**
* 操作的类型
*/
public enum OperateEnum {
UPDATE_USERNAME,
UPDATE_PASSWORD;
}
public interface UserOperate {
void updateUserInfo(OperateEnum type, UserInfo userInfo);
}
public class UserOperateImpl implements UserOperate{
@Override
public void updateUserInfo(OperateEnum type, UserInfo userInfo) {
if (type == OperateEnum.UPDATE_PASSWORD) {
// 修改密码
} else if(type == OperateEnum.UPDATE_USERNAME) {
// 修改用户名
}
}
}
遵循单一职责的方法
public interface UserOperate {
void updateUserName(UserInfo userInfo);
void updateUserPassword(UserInfo userInfo);
}
public class UserOperateImpl implements UserOperate {
@Override
public void updateUserName(UserInfo userInfo) {
// 修改用户名逻辑
}
@Override
public void updateUserPassword(UserInfo userInfo) {
// 修改密码逻辑
}
}
第一种实现是根据操作类型进行区分, 不同类型执行不同的逻辑. 把修改用户名和修改密码这两件事耦合在一起了. 如果客户端在操作的时候传错了类型, 那么就会发生错误.
第二种实现是我们推荐的实现方式. 修改用户名和修改密码逻辑分开. 各自执行各自的职责, 互不干扰. 功能清晰明了.
由此可见, 第二种设计是符合单一职责原则的. 这是在方法层面实现单一职责原则.
【接口层面】
假设一个场景, 大家一起做家务, 张三扫地, 李四买菜. 李四买完菜回来还得做饭. 这个逻辑怎么实现呢?
/**
* 做家务
*/
public interface HouseWork {
// 扫地
void sweepFloor();
// 购物
void shopping();
}
public class Zhangsan implements HouseWork{
@Override
public void sweepFloor() {
// 扫地
}
@Override
public void shopping() {
}
}
public class Lisi implements HouseWork{
@Override
public void sweepFloor() {
}
@Override
public void shopping() {
// 购物
}
}
首先定义了一个做家务的接口, 定义两个方法扫地和买菜. 张三扫地, 就实现扫地接口. 李四买菜, 就实现买菜接口. 然后李四买完菜回来还要做饭, 于是就要在接口类中增加一个方法cooking. 张三和李四都重写这个方法, 但只有李四有具体实现.
这样设计本身就是不合理的.
首先: 张三只扫地, 但是他需要重写买菜方法, 李四不需要扫地, 但是李四也要重写扫地方法.
第二: 这也不符合开闭原则. 增加一种类型做饭, 要修改3个类. 这样当逻辑很复杂的时候, 很容易引起意外错误.
上面这种设计不符合单一职责原则, 修改一个地方, 影响了其他不需要修改的地方.
/**
* 做家务
*/
public interface Hoursework {
}
public interface Shopping extends Hoursework{
// 购物
void shopping();
}
public interface SweepFloor extends Hoursework{
// 扫地
void sweepFlooring();
}
public class Zhangsan implements SweepFloor{
@Override
public void sweepFlooring() {
// 张三扫地
}
}
public class Lisi implements Shopping{
@Override
public void shopping() {
// 李四购物
}
}
上面做家务不是定义成一个接口, 而是将扫地和做家务分开了. 张三扫地, 那么张三就实现扫地的接口. 李四购物, 李四就实现购物的接口. 后面李四要增加一个功能做饭. 那么就新增一个做饭接口, 这次只需要李四实现做饭接口就可以了.
public interface Cooking extends Hoursework{
void cooking();
}
public class Lisi implements Shopping, Cooking{
@Override
public void shopping() {
// 李四购物
}
@Override
public void cooking() {
// 李四做饭
}
}
我们看到张三没有实现多余的接口, 李四也没有. 而且当新增功能的时候, 只影响了李四, 并没有影响张三.
这就是符合单一职责原则. 一个类只做一件事. 并且他的修改不会带来其他的变化.
【类层面】
从类的层面来讲, 没有办法完全按照单一职责原来来拆分. 换种说法, 类的职责可大可小, 不想接口那样可以很明确的按照单一职责原则拆分. 只要符合逻辑有道理即可.
public interface UserOperate {
void login(UserInfo userInfo);
void register(UserInfo userInfo);
void logout(UserInfo userInfo);
}
public class UserOperateImpl implements UserOperate{
@Override
public void login(UserInfo userInfo) {
// 用户登录
}
@Override
public void register(UserInfo userInfo) {
// 用户注册
}
@Override
public void logout(UserInfo userInfo) {
// 用户登出
}
}
如果按照单一职责原则拆分, 也可以拆分为下面的形式
public interface Register {
void register();
}
public interface Login {
void login();
}
public interface Logout {
void logout();
}
public class RegisterImpl implements Register{
@Override
public void register() {
}
}
public class LoginImpl implements Login{
@Override
public void login() {
// 用户登录
}
}
public class LogoutImpl implements Logout{
@Override
public void logout() {
}
}
像上面这样写可不可以呢? 其实也可以, 就是类很多. 如果登录、注册、注销操作代码很多, 那么可以这么写.
总结来说,遵守单一职责原则有助于提高代码质量、可读性、可复用性和可维护性。它是良好软件设计的基础之一,能够帮助开发者构建高效、健壮且易于维护的软件系统。
参考材料:https://developer.aliyun.com/article/921552?spm=a2c6h.24874632.expert-profile.74.1ac62afeKHP9JB