如需下载源码,请访问
https://github.com/fengchuanfang/Single_Responsibility_Principle
文章原创,转载请注明出处:
设计模式心法之单一职责原则
单一职责原则(Single Responsibility Principle,SRP)
——设计模式基本原则之一
定义:
应该有且仅有一个原因引起类的变更(There should never be more than one reason for a class to change)
单一职责原则为我们提供了一个编写程序的准则,要求我们在编写类,抽象类,接口时,要使其功能职责单一纯碎,将导致其变更的因素缩减到最少。
如果一个类承担的职责过多,就等于把这些职责耦合在一起。一个职责的变化可能会影响或损坏其他职责的功能。而且职责越多,这个类变化的几率就会越大,类的稳定性就会越低。
在软件开发中,经常会遇到一个功能类T负责两个不同的职责:职责P1,职责P2。现因需求变更需要更改职责P1来满足新的业务需求,当我们实现完成后,发现因更改职责P1竟导致原本能够正常运行的职责P2发生故障。而修复职责P2又不得不更改职责P1的逻辑,这便是因为功能类T的职责不够单一,职责P1与职责P2耦合在一起导致的。
例如下面的工厂类,负责 将原料进行预处理然后加工成产品X和产品Y。
public class Factory {
private String preProcess(String material){
return "*"+material+"*";
}
public String processX(String material) {
return preProcess(material) +"加工成:产品X";
}
public String processY(String material) {
return preProcess(material) +"加工成:产品Y";
}
}
现因市场需求,优化产品X的生产方案,需要改变原料预处理的方式
将预处理方法
private String preProcess(String material){
return "*"+material+"*";
}
改为
private String preProcess(String material){
return "#"+material+"#";
}
在以下场景类中运行
public class Client {
public static void main(String args[]) {
Factory factory = new Factory();
System.out.println(factory.processX("原料"));
System.out.println(factory.processY("原料"));
}
}
运行结果如下:
修改前:
*原料*加工成:产品X
*原料*加工成:产品Y
修改后:
#原料#加工成:产品X
#原料#加工成:产品Y
从运行结果中可以发现,在使产品X可以达到预期生产要求的同时,也导致了产品Y的变化,但是产品Y的变化并不在预期当中,这便导致程序运行错误甚至崩溃。
为了避免这种问题的发生,我们在软件开发中,应当注重各职责间的解耦和增强功能类的内聚性,来实现类的职责的单一性。
切断职责P1与职责P2之间的关联(解耦),然后将职责P1封装到功能类T1中,将职责P2封装到功能类T2中,使职责P1与职责P2分别由各自的独立的类来完成(内聚)。
按照单一职责原则可以将上面的工厂类按照以下方式进行分解
首先,定义一个抽象工厂类AFactory,有预加工preProcess和加工process方法,如下:
public abstract class AFactory {
protected abstract String preProcess(String material);
public abstract String process(String material);
}
由工厂类FactoryX继承自AFactory,专门生产产品X,如下:
public class FactoryX extends AFactory{
@Override
protected String preProcess(String material) {
return "*"+material+"*";
}
@Override
public String process(String material) {
return preProcess(material) +"加工成:产品X";
}
}
由工厂类FactoryY继承自AFactory,专门生产产品Y,如下:
public class FactoryY extends AFactory{
@Override
protected String preProcess(String material) {
return "*"+material+"*";
}
@Override
public String process(String material) {
return preProcess(material) +"加工成:产品Y";
}
}
场景类中调用如下:
public class Client {
public static void main(String args[]) {
produce(new FactoryX());
produce(new FactoryY());
}
private static void produce(AFactory factory) {
System.out.println(factory.process("原料"));
}
}
现在优化产品X的生产方案,像之前那样改变原料预处理的方式,只需要改变类FactoryX中preProcess方法,对比修改前与修改后运行如下:
修改前:
*原料*加工成:产品X
*原料*加工成:产品Y
修改后:
#原料#加工成:产品X
*原料*加工成:产品Y
由结果可知,优化产品X的生产方案,不会再影响产品Y。
尽管我们在开发设计程序时,总想着要使类的职责单一,保证类的高内聚低耦合,但是很多耦合往往发生在不经意间,其原因为:类的职责扩散。
由于软件的迭代升级,类的某一职责会被分化为颗粒度更细的多个职责。这种分化分为横向细分和纵向细分两种形式。
例如下面的工厂类负责将原料多次加工处理后生产产品X
public class Factory {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
private String packaging(String material) {
return process(material) + "包装——>";
}
public String processX(String material) {
return packaging(material) + "产品X";
}
}
场景类中调用
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
System.out.println(factory.processX("原料"));
}
}
运行结果如下:
*原料——>加工——>包装——>产品X
横向细分
现因业务拓展,工厂增加生产产品Y,产品Y与产品X除了包装不同之外,其它都一样,换汤不换药。
秉着代码复用,少敲键盘,高效完成工作的原则,对工厂类Factory进行拓展,如下:
public class Factory {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
private String packaging(String material) {
return process(material) + "包装——>";
}
public String processX(String material) {
return packaging(material) + "产品X";
}
public String processY(String material) {
return packaging(material) + "产品Y";
}
}
场景类Client中,对代码进行调用,如下:
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
System.out.println(factory.processX("原料"));
System.out.println(factory.processY("原料"));
}
}
运行结果如下:
*原料——>加工——>包装——>产品X
*原料——>加工——>包裝——>产品Y
纵向细分
因业务拓展,工厂除了生产产品X,还生产半成品,简单包装一下就可以了,不需要贴上产品X的商标。
因为有现有的预处理,加工,包装方法,为了方便起见,直接将类Factory中的packaging方法,有private改为public,然后由不同的场景类调用,代码如下:
public class Factory {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
public String packaging(String material) {
return process(material) + "包装——>";
}
public String processX(String material) {
return packaging(material) + "产品X";
}
}
public class Client1 {
public static void main(String[] args) {
Factory factory = new Factory();
System.out.println(factory.processX("原料"));
}
}
public class Client2 {
public static void main(String[] args) {
Factory factory = new Factory();
System.out.println(factory.packaging("原料"));
}
}
运行Client1与Client2后,结果如下:
*原料——>加工——>包装——>产品X
*原料——>加工——>包装——>
这样之前的问题又出现了,如果优化产品X的生产方案,需要改变原料预处理的方式,同样也会牵连到其他生产过程的变化,无论是纵向的还是横向的。
所以,如果更改了原料预处理的方法,牵一发动全身的问题依然存在。
对于这个问题该怎么解决呢?
有很多人为了省事方便可能会采用下面的方法
对于横向细分的情况,可以用CP大法将类Factory进行拆分,分成FactoryX和FactoryY两个类,如下:
public class FactoryX {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
private String packaging(String material) {
return process(material) + "包装——>";
}
public String processX(String material) {
return packaging(material) + "产品X";
}
}
public class FactoryY {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
private String packaging(String material) {
return process(material) + "包装——>";
}
public String processY(String material) {
return packaging(material) + "产品Y";
}
}
纵向细分的情况可以再拆出一个类
public class SubFactory {
private String preProcess(String material) {
return "*" + material + "——>";
}
private String process(String material) {
return preProcess(material) + "加工——>";
}
public String packaging(String material) {
return process(material) + "包装——>";
}
}
这样虽然解决了因职责横向细分或纵向细分导致的牵一发动全身的问题,但是这样就使代码完全失去复用性了,而且FactoryX,FactoryY的职责真正单一了吗?
从横向上看,FactoryX只生产产品X,FactoryY只生产产品Y来看,类的职责是单一了,
但是从纵向上看,两个类都有颗粒度更小的四个职责,原料预处理,加工,包装,产出成品,
SubFactory也有颗粒度更小的三个职责,原料预处理,加工,包装。
单一职责要求我们在编写类,抽象类,接口时,要使其功能职责单一纯碎,将导致其变更的因素缩减到最少。
按照这个原则对于工厂类Factory我们重新调整一下实现方案
首先,将四个职责抽取成以下四个接口
//预处理接口
public interface IPreProcess {
String preProcess(String material);
}
//加工接口
public interface IProcess {
String process(String material);
}
//包装接口
public interface IPackaging {
String packaging(String semiProduct);
}
//产出成品接口
public interface IFactory {
String process(String product);
}
然后,有四个职责类分别实现这四个接口,如下
//原料预处理类
public class PreProcess implements IPreProcess{
@Override
public String preProcess(String material) {
return "*" + material + "——>";
}
}
//加工类
public class Process implements IProcess {
private IPreProcess preProcess;
public Process(IPreProcess preProcess) {
this.preProcess = preProcess;
}
@Override
public String process(String material) {
return this.preProcess.preProcess(material) + "加工——>";
}
}
//包装类
public class Packaging implements IPackaging {
private IProcess process;
public Packaging(IProcess process) {
this.process = process;
}
@Override
public String packaging(String material) {
return this.process.process(material) + "包装——>";
}
}
//产出成品类
public class FactoryX implements IFactory{
private Packaging packaging;
public FactoryX(Packaging packaging) {
this.packaging = packaging;
}
@Override
public String process(String material) {
return this.packaging.packaging(material)+ "产品X";
}
}
场景类中调用代码如下:
public class Client {
public static void main(String[] args) {
IFactory factory = new FactoryX(new Packaging(new Process(new PreProcess())));
System.out.println(factory.process("原料"));
}
}
如果增加产品Y的生产(产品Y与产品X除了包装不同之外,其它都一样)
可以增加一个IFactory的实现类FactoryY
//产出成品类
public class FactoryY implements IFactory{
private Packaging packaging;
public FactoryY(Packaging packaging) {
this.packaging = packaging;
}
@Override
public String process(String material) {
return this.packaging.packaging(material)+ "产品Y";
}
}
场景类中调用如下:
public class Client {
public static void main(String[] args) {
IFactory factoryX = new FactoryX(new Packaging(new Process(new PreProcess())));
System.out.println(factoryX.process("原料"));
IFactory factoryY = new FactoryY(new Packaging(new Process(new PreProcess())));
System.out.println(factoryY.process("原料"));
}
}
运行结果如下:
*原料——>加工——>包装——>产品X
*原料——>加工——>包装——>产品Y
如果因市场需求,优化产品X的生产方案,改变原料预处理的方式
将
"*" + material + "——>";
改成
"#" + material + "——>";
可以增加一个原料预处理接口IPreProcess的实现类PreProcessA,如下
//原料预处理类
public class PreProcessA implements IPreProcess{
@Override
public String preProcess(String material) {
return "#" + material + "——>";
}
}
场景类更改如下(不仔细看可能找不到改的哪):
public class Client {
public static void main(String[] args) {
IFactory factoryX = new FactoryX(new Packaging(new Process(new PreProcessA())));
System.out.println(factoryX.process("原料"));
IFactory factoryY = new FactoryY(new Packaging(new Process(new PreProcess())));
System.out.println(factoryY.process("原料"));
}
}
运行结果如下:
#原料——>加工——>包装——>产品X
*原料——>加工——>包装——>产品Y
可以看到,在优化产品X的生产方案,改变原料预处理的方式的过程中,并没有改变产品Y的正常生产。
如果想生产产品X的半成品的话,不需更改生产代码,只需在场景类中直接调用即可,如下
public class Client {
public static void main(String[] args) {
IFactory factoryX = new FactoryX(new Packaging(new Process(new PreProcessA())));
System.out.println(factoryX.process("原料"));
IFactory factoryY = new FactoryY(new Packaging(new Process(new PreProcess())));
System.out.println(factoryY.process("原料"));
IPackaging packagingX = new Packaging(new Process(new PreProcess()));
System.out.println(packagingX.packaging("原料"));
}
}
运行结果如下:
#原料——>加工——>包装——>产品X
*原料——>加工——>包装——>产品Y
#原料——>加工——>包装——>
即使哪天市场需求再变化,再优化产品X的生产方案,改变原料预处理的方式
只需再添加个类,实现预处理接口IPreProcess即可,如下
//原料预处理类
public class PreProcessB implements IPreProcess{
@Override
public String preProcess(String material) {
return "%" + material + "——>";
}
}
场景类更改如下:
public class Client {
public static void main(String[] args) {
IFactory factoryX = new FactoryX(new Packaging(new Process(new PreProcessB())));
System.out.println(factoryX.process("原料"));
IFactory factoryY = new FactoryY(new Packaging(new Process(new PreProcess())));
System.out.println(factoryY.process("原料"));
IPackaging packagingX = new Packaging(new Process(new PreProcessA()));
System.out.println(packagingX.packaging("原料"));
}
}
运行结果如下:
%原料——>加工——>包装——>产品X
*原料——>加工——>包装——>产品Y
#原料——>加工——>包装——>
依然不会对其他逻辑造成影响
单一职责原则不是单单的将类的功能进行颗粒化拆分,拆分的越细越好,这样虽然可以保证类的功能职责的单一性,但是也会导致类的数量暴增,功能实现复杂,一个功能需要多个功能细分后的类来完成,会造成类的调用繁琐,类间关系交织混乱,后期维护困难。所以单一职责原则并不是要求类的功能拆分的越细越好,对类的功能细分需要有个度,细分到什么程度才是最合适呢,细分到在应对未来的拓展时,有且仅有一个因素导致其变更。
在一个项目中,类不是孤立存在的,是需要与其他类相互配合实现复杂功能的。所以类的职责和变更因素都是难以衡量的,会因所属项目的不同而不同。需要在长期的开发经验中慢慢摸索。