单一职责原则的英文名称是Single Responsibility Principle,简称是SRP,顾名思义:一个类只负责一项职责。它的官方定义是
单一职责原则(Single Responsibility Principle, SRP),There should never be more than one reason for a class to change(有且仅有一个原因引起类的变更)
一、单一职责的案例讨论
单单从字面意思上来看,单一职责原则的执行粒度是“类”,也就是一个“类”只负责一项职责,但是很明显我们在实际执行过程中很难做到这点。
1、类的单一职责案例
在《设计模式之禅(第2版)》中,作者举了个例子,有个接口Iphone定义如下
这个接口定义了三个方法
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂电话
public void hangup();
}
拨通电话方法dial和挂断电话方法hangup应该拆到一个类里,通话方法chat应该拆到另外一个单独的类里,最后变成如下图所示
这是个职责分明的电话类图,但是一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用,组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式呢,而且还增加了类的复杂性,多了两个类,所以作者又优化了下
一个类实现了两个接口,把两个职责融合在一个类中,诶?这不是违反了单一职责原则吗?应该说我们是“半违反”单一职责原则了,因为我们是面向接口编程,我们对外公布的是接口而不是实现类,对使用者来说,Phone这个实例可以是IConnectionManager,也可以是IDataTransfer。
看完这个案例,其实我内心不是很认同。 毕竟,类似于
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂电话
public void hangup();
}
这种代码,是我们面向对象编程第一课就写进教科书里的代码,而且如果非要说,无论是打电话、聊天、挂断电话,这本都是“打电话”这一个过程中发生的事情,是否可以理解为它们统统属于手机的“单一职责”呢,我只能认为是作者举的例子不是很恰当。
但是,紧接着作者又说
看过电话这个例子后,是不是想反思一下了,我以前的设计是不是有点问题了?不,不是的,不要怀疑自己的技术能力,单一职责原则最难划分的就是职责。一个职责一个接口,但问题是“职责”没有一个量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。
好家伙,这作者自己打脸打的这么快
这引出了关于单一职责原则最大的争议点:“职责”没有一个量化的标准,一千个人眼中有一千个哈姆雷特,都觉得自己是对的,就比如上面举的这个例子,作者觉得拆两个接口才合理,我觉得一开始的封装到一个类中就行(从结果上来看,作者还是最后封装到了一个类里),我们的意见就不一样,其实我还有另外的想法:如果这都不合理,java的多重继承机制干啥用的?
我再举一个实际开发中的例子,我们使用mybatis组件,一定要写一堆的mapper文件,每个mapper文件都可以说是围绕着某个实体类对应的表做的CRUD操作,其形式如下所示:
@Mapper
public interface UserMapper{
public void saveUser(User user);
public void updateUser(User user);
public User getUser(Long id);
public int deleteUser(Long id);
}
我认为这是我们开发时遵守单一职责原则的表现,如果我们在UserMapper中新增了一个方法,叫selectStudentById(),那我们肯定会觉得不合适,这不合适的原因就是破坏了单一职责原则。
2、方法的单一职责案例
单一职责适用于接口、类,同时也适用于方法,**也就是一个方法尽可能做一件事情。**作者举了个例子:一个方法修改用户密码,不要把这个方法放到“修改用户信息”方法中,因为修改用户信息这个方法的颗粒度太粗了。
我在这里也举个例子:我经常在一些复杂业务中看到类似如下这种代码
public User getUserByMobile(String mobile){
User user = getUserDetail(mobile);
//获取用户其它信息
UserCompany compay = getCompany(mobile);
//如果不为空,则先更新用户的企业信息
if(Objects.nonNull(company)){
updateUserCompay(company);
user.setCompany(company);
}
return user;
}
以上代码是一段查询用户信息的方法,这段方法只是看名字谁也想不到,里面竟然还有更新用户信息的逻辑,这实际上违背了单一职责原则,让代码的可维护性变的极差。很明显这段代码需要重构,重构方法要根据业务灵活判断了。
二、总结
1、遵守单一职责原则的好处
单一职责原则有什么好处:
● 类的复杂性降低,实现什么职责都有清晰明确的定义;
● 可读性提高,复杂性降低,那当然可读性提高了;
● 可维护性提高,可读性提高,那当然更容易维护了;
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
单一职责原则实际上是“高内聚”的提现。
2、如何遵守单一职责原则
其实就是合理的职责分解,相同的职责放到一起,不同的职责分解到不同的接口和实现中去,这个是最容易也是最难运用的原则,关键还是要从业务出发,从需求出发,识别出同一种类型的职责
需要说明的一点是:单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责
还是难在那一句话:“职责”没有一个量化的标准,具体实施肯定会因人而异,《设计模式之禅(第2版)》中给的最佳实践是
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
END.
注意:本文归作者所有,未经作者允许,不得转载