暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面向对象设计原则之单一职责原则(SRP)

程序媛和她的猫 2020-08-01
612

(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类图。

图1
图2

(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("轮船");
}
}

运行结果:

汽车在公路上运行。。。

飞机在天空中运行。。。

轮船在水中运行。。。

总结:以类的维度进行职责的拆分,既有优点,也有缺点。

  • 优点
  1. 既遵循了单一职责原则,也遵循了开闭原则,当增加一种交通工具的时候,只需要增加一个XXXVehicle类,而不需要修改原有的Vehicle类。
  • 缺点
  1. 以类的维度进行拆分,容易造成类爆炸。
  2. 重构的成本比较大,服务端改动,客户端也要改动,见程序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("轮船");
}
}

运行结果:

汽车在公路上运行。。。

飞机在天空中运行。。。

轮船在水中运行。。。

总结:以方法的维度进行职责的拆分,优点和缺点。

  • 优点
  1. 重构的成本比较小,服务端改动,客户端不需要改动,见程序5中的Client类。
  • 缺点
  1. 违反了开闭原则,当我们想要增加一种交通工具的时候,需要修改原有的Vehicle类。

(8) 单一职责的优点

  • 降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 降低变更引起的风险,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

(9) 单一职责的注意事项

对类的拆分要"适可而止",切不可将一个类划分地太过于细致,否则会造成类爆炸,类的增多,不仅会消耗内存,同时也会对程序的运行效率有所影响。



文章转载自程序媛和她的猫,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论