设计模式心法之单一职责原则

如需下载源码,请访问
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
#原料——>加工——>包装——>

依然不会对其他逻辑造成影响

单一职责原则不是单单的将类的功能进行颗粒化拆分,拆分的越细越好,这样虽然可以保证类的功能职责的单一性,但是也会导致类的数量暴增,功能实现复杂,一个功能需要多个功能细分后的类来完成,会造成类的调用繁琐,类间关系交织混乱,后期维护困难。所以单一职责原则并不是要求类的功能拆分的越细越好,对类的功能细分需要有个度,细分到什么程度才是最合适呢,细分到在应对未来的拓展时,有且仅有一个因素导致其变更。

在一个项目中,类不是孤立存在的,是需要与其他类相互配合实现复杂功能的。所以类的职责和变更因素都是难以衡量的,会因所属项目的不同而不同。需要在长期的开发经验中慢慢摸索。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容