单一职责原则(Single Responsibility Principle, SRP)的定义是:指一个类或者模块应该有且只有一个改变的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。
单一职责原则 意义
单一职责原则告诉我们:一个类不能做太多的东西。在软件系统中,一个类(一个模块、或者一个方法)承担的职责越多,那么其被复用的可能性就会越低。
单一职责原则 实现:
一个很典型的例子就是万能类。其实可以说一句大实话:任何一个常规的MVC项目,在极端的情况下,可以用一个类(甚至一个方法)完成所有的功能。但是这样做就会严重耦合,甚至牵一发动全身。一个类承(一个模块、或者一个方法)担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
不过说实话,其实有的时候很难去衡量一个类的职责,主要是很难确定职责的粒度。这一点不仅仅体现在一个类或者一个模块中,也体现在采用微服务的分布式系统中。这也就是为什么我们在实施微服务拆分的时候经常会撕逼:"这个功能不应该发在A服务中,它不做这个领域的东西,应该放在B服务中"诸如此类的争论。存在争论是合理的,不过最好不要不了了之,而应该按照领域定义好每个服务的职责(职责的粒度最好找业务和架构专家咨询),得出相对合理的职责分配。
下面通过一个很简单的实例说明一下单一职责原则:
在一个项目系统代码编写的时候,由于历史原因和人为的不规范,导致项目没有分层,一个Service类的伪代码是这样的:
public class Service {
public UserDTO findUser(String name){
Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM t_user WHERE name = ?");
preparedStatement.setObject(1, name);
User user = //处理结果
UserDTO dto = new UserDTO();
//entity值拷贝到dto
return dto;
}
}
这里出现一个问题,Service做了太多东西,包括数据库连接的管理,Sql的执行这些业务层不应该接触到的逻辑,更可怕的是,例如到时候如果数据库换成了Oracle,这个方法将会大改。因此,拆分出新的DataBaseUtils类用于专门管理数据库资源,Dao类用于专门执行查询和查询结果封装,改造后Service类的伪代码如下:
public class Service {
private Dao dao;
public UserDTO findUser(String name){
User user = dao.findUserByName(name);
UserDTO dto = new UserDTO();
// entity值拷贝到dto
return dto;
}
}
现在,如果有查询封装的变动只需要修改Dao类,数据库相关变动只需要修改DataBaseUtils类,每个类的职责分明。这个时候,如果我们要把底层的存储结构缓成Redis或者MongoDB怎么办,这样显然要重建整个Dao类,这种情况下,需要进行接口隔离,下面分析接口隔离原则的时候再详细分析。