(1) 定义
就一个类(接口或方法)而言,应该仅有一个引起它变化的原因,即一个类(接口或方法)只负责一个职责。
(2) 违反单一职责原则可能产生的问题
如果一个类有一个以上的职责,这些职责就耦合在一起了,那么当一个职责发生变化时,可能会影响其他的职责。比如类T负责两个不同的职责,职责P1和职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。 如果一个类有一个以上的职责,这些职责就耦合在一起了,会影响程序的复用性。
(3) 解决方法
将类T拆成两个类,类T1和类T2,在类T1中完成职责P1功能,在类T2中完成职责P2功能。这样,当职责P1需求发生改变,只需要修改类T1,不会使职责P2发生故障风险,同理,当职责2需求发生改变,只需要修改类T2,不会使职责P1发生故障风险。
(4) 什么是职责
在单一职责原则的定义中,我们把职责定义为“变化的原因”。刚开始接触单一职责原则的时候,我常认为一个类中一个方法就是一个职责,这是错误的,职责要看这个类中做了几种事,就有几个职责。
见程序1中的Phone接口(电话接口),刚开始我认为这个接口包含四个职责,即每个方法是一个职责,实际上是包含两个职责,第一个职责是连接管理,第二个职责是数据通信,dial(拨号,打开连接)和hangUp(挂机,关闭连接)函数进行调制解调器的连接处理,send(发送消息)和recv(接收消息)函数进行数据通信。
// 程序1:Phone.java--违反SRP原则public interface Phone {public void dial(String pno);public void hangUp();public void send(char c);public char recv();}
(5) 当一个类中包含两个(及两个以上)职责,什么情况下需要分离职责,什么情况下不需要分离职责。
如果应用程序的变化,总是导致这两个职责同时变化,那么就不必分离它们。如果应用程序的变化,会导致一个职责频繁的变化,而另一个职责基本不会变化,此时就应该分离它们。图1是未分离职责的UML类图,图2是分离了职责的UML类图。


(6) 如何分离职责?
分离职责,有三个维度,类、接口和方法。以类和接口为维度,对其内部的方法进行分类,可能一个方法就是一个职责,也可能多个方法是一个职责。以方法为维度,对其内部的逻辑进行分类,可能一个方法只有一个职责,也可能一个方法包含多个职责。
(7) 单一职责原则案例
用一个类来描述交通工具运行这个场景。
// 程序2// 交通工具类public class Vehicle {/*** 运行* @param 交通工具名字*/public void run(String vehicle){System.out.println(vehicle + "在公路上运行。。。");}}public class Client {public static void main(String[] args){Vehicle vehicle = new Vehicle();vehicle.run("汽车");}}
运行结果:汽车在公路上运行。。。
程序上线后,后续要求接入飞机这种交通工具,Test类如下。
public class Client {public static void main(String[] args){Vehicle vehicle = new Vehicle();vehicle.run("飞机");}}
运行结果:飞机在公路上运行。。。
显然是不对的,原因是Vehicle的run方法违反了单一职责原则,此时,我们会进行重构,使其满足单一职责原则。
方法一、以类的维度进行职责的拆分
根据交通工具运行方式的不同,将Vehicle(交通工具)分解成不同的类,分为RoadVehicle(公路交通工具)、AirVehicle(空中交通工具)、WaterVehicle(水中交通工具)等等。
// 程序4// 公路交通工具public class RoadVehicle {public void run(String vehicle){System.out.println(vehicle + "在公路上运行。。。");}}// 空中交通工具public class AirVehicle {public void run(String vehicle){System.out.println(vehicle + "在天空中运行。。。");}}// 水中交通工具public class WaterVehicle {public void run(String vehicle){System.out.println(vehicle + "在水中运行。。。");}}public class Client {public static void main(String[] args){RoadVehicle roadVehicle = new RoadVehicle();roadVehicle.run("汽车");AirVehicle airVehicle = new AirVehicle();airVehicle.run("飞机");WaterVehicle waterVehicle = new WaterVehicle();waterVehicle.run("轮船");}}
运行结果:
汽车在公路上运行。。。
飞机在天空中运行。。。
轮船在水中运行。。。
总结:以类的维度进行职责的拆分,既有优点,也有缺点。
优点
既遵循了单一职责原则,也遵循了开闭原则,当增加一种交通工具的时候,只需要增加一个XXXVehicle类,而不需要修改原有的Vehicle类。
缺点
以类的维度进行拆分,容易造成类爆炸。 重构的成本比较大,服务端改动,客户端也要改动,见程序4中的Client类。
针对以类维度进行职责拆分的缺点,我们换一种方法进行拆分,以方法的维度进行拆分。
方法二、以方法的维度进行职责的拆分
根据交通工具运行方式的不同,将Vehicle中的run方法分解成不同的方法,runRoad方法、runAir方法、runWater方法等等
// 程序5// 交通工具类public class Vehicle {public void runRoad(String vehicle){System.out.println(vehicle + "在公路上运行。。。");}public void runAir(String vehicle){System.out.println(vehicle + "在天空中运行。。。");}public void runWater(String vehicle){System.out.println(vehicle + "在水中运行。。。");}}public class Client {public static void main(String[] args){Vehicle vehicle = new Vehicle();vehicle.runRoad("汽车");vehicle.runAir("飞机");vehicle.runAir("轮船");}}
运行结果:
汽车在公路上运行。。。
飞机在天空中运行。。。
轮船在水中运行。。。
总结:以方法的维度进行职责的拆分,优点和缺点。
优点
重构的成本比较小,服务端改动,客户端不需要改动,见程序5中的Client类。
缺点
违反了开闭原则,当我们想要增加一种交通工具的时候,需要修改原有的Vehicle类。
(8) 单一职责的优点
降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多; 提高类的可读性,提高系统的可维护性; 降低变更引起的风险,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
(9) 单一职责的注意事项
对类的拆分要"适可而止",切不可将一个类划分地太过于细致,否则会造成类爆炸,类的增多,不仅会消耗内存,同时也会对程序的运行效率有所影响。




