这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情
系列文章|源码
定义-是什么
单一职责原则(Single Responsibility Principle,SRP)是指:所有的对象都应该有单一的职责,它提供的所有的服务也都仅围绕着这个职责。换句话说就是:一个类而言,应该仅有一个引起它变化的原因,永远不要让一个类存在多个改变的理由。
单一职责原则的核心就是 解耦 和 增强内聚性。
思考-为什么
为什么要遵守单一职责原则?
- 提高类的可维护性和可读写性
一个类的职责少了,复杂度降低了,代码就少了,可读性也就好了,可维护性自然就高了。
- 提高系统的可维护性
系统是由类组成的,每个类的可维护性高,相对来讲整个系统的可维护性就高。当然,前提是系统的架构没有问题。
- 降低变更的风险
如果在一个类中可能会有多个发生变化的东西,这样的设计会带来风险, 我们尽量保证只有一个可以变化,其他变化的就放在其他类中,这样的好处就是提高内聚,降低耦合。
违反单一职责原则有什么坏处?
如果一个类承担的职责过多,等于把这些职责耦合在了一起;一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。
这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
应用-怎么用
判断标准
不同的应用场景,不同阶段的需求背景,不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。实际上,一些侧面的判断指标更具有指导意义和可执行性,比如出现下面这些情况可能说明这类的设计不满足单一职责原则:
- 类中的代码行数,函数或者属性过多;
- 类依赖的其他类过多,或者依赖的类的其他类过多;
- 私有方法过多;
- 比较难给类起一个合适的名字;
- 类中大量的方法都集中操作类的某几个属性;
方法层面--单一职责原则
需求:完成用户的用户名和密码修改功能
针对上述需求,有多种实现方案,实现如下:
代码仓库地址:github.com/tyronczt/de…
第一种方法
public enum OperateEnum {
UPDATE_USERNAME,
UPDATE_PASSWORD;
}
public interface IUserOperateService {
/**
* 修改用户信息
*
* @param type 操作类型
* @param userInfo 用户信息
*/
void updateUserInfo(OperateEnum type, UserInfo userInfo);
}
public class UserOperateServiceImpl implements IUserOperateService {
@Override
public void updateUserInfo(OperateEnum type, UserInfo userInfo) {
if (type == OperateEnum.UPDATE_PASSWORD) {
// 修改密码
System.out.println("正在修改密码");
} else if (type == OperateEnum.UPDATE_USERNAME) {
// 修改用户名
System.out.println("正在修改用户名");
}
}
}
第二种方法
public interface IUserOperateService {
/**
* 修改用户名
*
* @param userInfo 用户信息
*/
void updateUserName(UserInfo userInfo);
/**
* 修改密码
*
* @param userInfo 用户信息
*/
void updateUserPassword(UserInfo userInfo);
}
public class UserOperateServiceImpl implements IUserOperateService {
@Override
public void updateUserName(UserInfo userInfo) {
System.out.println("正在修改用户名");
}
@Override
public void updateUserPassword(UserInfo userInfo) {
System.out.println("正在修改密码");
}
}
以上方法都可以实现上述的需求,第一种方法根据方法中的参数去区分执行不同的逻辑,将两个逻辑耦合在了一起,这种实现的弊端也很明显:一是如果客户端传错类型,会导致后续的逻辑无法执行;二是这种实现的可维护性不高,当新增逻辑后会变动原来代码;第二种方法是相对推荐的方法,修改用户名和密码的逻辑分开,各自执行各自的职责,互补干扰,也有利于后期的维护;
由此可见,第二种方法是符合单一职责原则的,这是在方法层面实现的单一职责原则。
接口层面--单一职责原则
需求:大家一起做家务, 张三扫地, 李四买菜. 李四买完菜回来还得做饭。
仓库代码地址:github.com/tyronczt/de…
第一种方法
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() {
// 买菜
}
}
方法一实现了需求的前半句话,由于设计不合理导致后半句需求无法实现。在实现类有不同的需求时,要拆分接口,而不是将功能设计到一个接口中,不符合接口设计的单一职责原则。
第二种方法
public interface HouseWork {
}
public interface Shopping extends HouseWork {
// 购物
void shopping();
}
public interface SweepFloor extends HouseWork {
// 扫地
void sweepFlooring();
}
public interface Cooking extends HouseWork {
// 做饭
void cooking();
}
public class Zhangsan implements SweepFloor {
@Override
public void sweepFlooring() {
// 张三扫地
}
}
public class Lisi implements Shopping, Cooking {
@Override
public void shopping() {
// 李四购物
}
@Override
public void cooking() {
// 李四做饭
}
}
方法二将每个动作定义为接口并进行实现,符合单一职责原则,一个类只做一件事,并且他的修改不会带来其他的变化,便于后续的系统维护。
类层面--单一职责原则
从类的层面来讲, 没有办法完全按照单一职责原来来拆分,换种说法: 类的职责可大可小,不像接口那样可以很明确的按照单一职责原则拆分,只要符合逻辑有道理即可。
需求:完成用户相关逻辑。
仓库代码地址:github.com/tyronczt/de…
第一种方法:
public interface IUserOperateService {
// 用户登录
void login(UserInfo userInfo);
// 用户注册
void register(UserInfo userInfo);
// 用户登出
void logout(UserInfo userInfo);
}
public class UserOperateServiceImpl implements IUserOperateService {
@Override
public void login(UserInfo userInfo) {
// 用户登录
}
@Override
public void register(UserInfo userInfo) {
// 用户注册
}
@Override
public void logout(UserInfo userInfo) {
// 用户登出
}
}
第二种方法:
public interface ILoginService {
void login();
}
public interface ILogoutService {
void logout();
}
public interface IRegisterService {
void register();
}
public class LoginServiceImpl implements ILoginService {
@Override
public void login() {
// 用户登录
}
}
public class LogoutServiceImpl implements ILogoutService {
@Override
public void logout() {
// 用户登出
}
}
public class RegisterServiceImpl implements IRegisterService{
@Override
public void register() {
// 用户注册
}
}
方法1和方法2均可以实现上述的需求,在实际项目开发中,往往两种方法都会用到,当接口实现逻辑相对简单时,往往会使用第一种方法,当接口实现逻辑复杂时,则会采用第二种方法,具体情况还需具体分析。