設(shè)計模式心法之單一職責(zé)原則

如需下載源碼,請訪問
https://github.com/fengchuanfang/Single_Responsibility_Principle

文章原創(chuàng),轉(zhuǎn)載請注明出處:
設(shè)計模式心法之單一職責(zé)原則


單一職責(zé)原則(Single Responsibility Principle,SRP)

——設(shè)計模式基本原則之一

定義:

應(yīng)該有且僅有一個原因引起類的變更(There should never be more than one reason for a class to change)

單一職責(zé)原則為我們提供了一個編寫程序的準(zhǔn)則,要求我們在編寫類,抽象類,接口時,要使其功能職責(zé)單一純碎,將導(dǎo)致其變更的因素縮減到最少。

如果一個類承擔(dān)的職責(zé)過多,就等于把這些職責(zé)耦合在一起。一個職責(zé)的變化可能會影響或損壞其他職責(zé)的功能。而且職責(zé)越多,這個類變化的幾率就會越大,類的穩(wěn)定性就會越低。

在軟件開發(fā)中,經(jīng)常會遇到一個功能類T負(fù)責(zé)兩個不同的職責(zé):職責(zé)P1,職責(zé)P2?,F(xiàn)因需求變更需要更改職責(zé)P1來滿足新的業(yè)務(wù)需求,當(dāng)我們實現(xiàn)完成后,發(fā)現(xiàn)因更改職責(zé)P1竟導(dǎo)致原本能夠正常運行的職責(zé)P2發(fā)生故障。而修復(fù)職責(zé)P2又不得不更改職責(zé)P1的邏輯,這便是因為功能類T的職責(zé)不夠單一,職責(zé)P1與職責(zé)P2耦合在一起導(dǎo)致的。

例如下面的工廠類,負(fù)責(zé) 將原料進(jìn)行預(yù)處理然后加工成產(chǎn)品X和產(chǎn)品Y。

public class Factory {

    private String preProcess(String material){
        return "*"+material+"*";
    }
    public String processX(String material) {
        return preProcess(material) +"加工成:產(chǎn)品X";
    }

    public String processY(String material) {
        return preProcess(material) +"加工成:產(chǎn)品Y";
    }
}

現(xiàn)因市場需求,優(yōu)化產(chǎn)品X的生產(chǎn)方案,需要改變原料預(yù)處理的方式
將預(yù)處理方法

    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("原料"));
}
}

運行結(jié)果如下:

修改前:
*原料*加工成:產(chǎn)品X
*原料*加工成:產(chǎn)品Y
修改后:
#原料#加工成:產(chǎn)品X
#原料#加工成:產(chǎn)品Y

從運行結(jié)果中可以發(fā)現(xiàn),在使產(chǎn)品X可以達(dá)到預(yù)期生產(chǎn)要求的同時,也導(dǎo)致了產(chǎn)品Y的變化,但是產(chǎn)品Y的變化并不在預(yù)期當(dāng)中,這便導(dǎo)致程序運行錯誤甚至崩潰。

為了避免這種問題的發(fā)生,我們在軟件開發(fā)中,應(yīng)當(dāng)注重各職責(zé)間的解耦和增強(qiáng)功能類的內(nèi)聚性,來實現(xiàn)類的職責(zé)的單一性。

切斷職責(zé)P1與職責(zé)P2之間的關(guān)聯(lián)(解耦),然后將職責(zé)P1封裝到功能類T1中,將職責(zé)P2封裝到功能類T2中,使職責(zé)P1與職責(zé)P2分別由各自的獨立的類來完成(內(nèi)聚)。

按照單一職責(zé)原則可以將上面的工廠類按照以下方式進(jìn)行分解

首先,定義一個抽象工廠類AFactory,有預(yù)加工preProcess和加工process方法,如下:

public abstract class AFactory {

    protected abstract String preProcess(String material);

    public abstract String process(String material);
}

由工廠類FactoryX繼承自AFactory,專門生產(chǎn)產(chǎn)品X,如下:

public class FactoryX extends AFactory{

    @Override
    protected String preProcess(String material) {
        return "*"+material+"*";
    }

    @Override
    public String process(String material) {
        return preProcess(material) +"加工成:產(chǎn)品X";
    }

}

由工廠類FactoryY繼承自AFactory,專門生產(chǎn)產(chǎn)品Y,如下:

public class FactoryY extends AFactory{

    @Override
    protected String preProcess(String material) {
        return "*"+material+"*";
    }

    @Override
    public String process(String material) {
        return preProcess(material) +"加工成:產(chǎn)品Y";
    }

}

場景類中調(diào)用如下:


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("原料"));
    }

}

現(xiàn)在優(yōu)化產(chǎn)品X的生產(chǎn)方案,像之前那樣改變原料預(yù)處理的方式,只需要改變類FactoryX中preProcess方法,對比修改前與修改后運行如下:

修改前:
*原料*加工成:產(chǎn)品X
*原料*加工成:產(chǎn)品Y
修改后:
#原料#加工成:產(chǎn)品X
*原料*加工成:產(chǎn)品Y

由結(jié)果可知,優(yōu)化產(chǎn)品X的生產(chǎn)方案,不會再影響產(chǎn)品Y。

盡管我們在開發(fā)設(shè)計程序時,總想著要使類的職責(zé)單一,保證類的高內(nèi)聚低耦合,但是很多耦合往往發(fā)生在不經(jīng)意間,其原因為:類的職責(zé)擴(kuò)散。
由于軟件的迭代升級,類的某一職責(zé)會被分化為顆粒度更細(xì)的多個職責(zé)。這種分化分為橫向細(xì)分和縱向細(xì)分兩種形式。

例如下面的工廠類負(fù)責(zé)將原料多次加工處理后生產(chǎn)產(chǎn)品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) + "產(chǎn)品X";
    }
}

場景類中調(diào)用

public class Client {

    public static void main(String[] args) {
        Factory factory = new Factory();
        System.out.println(factory.processX("原料"));
    }

}

運行結(jié)果如下:

*原料——>加工——>包裝——>產(chǎn)品X

橫向細(xì)分

現(xiàn)因業(yè)務(wù)拓展,工廠增加生產(chǎn)產(chǎn)品Y,產(chǎn)品Y與產(chǎn)品X除了包裝不同之外,其它都一樣,換湯不換藥。

秉著代碼復(fù)用,少敲鍵盤,高效完成工作的原則,對工廠類Factory進(jìn)行拓展,如下:

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) + "產(chǎn)品X";
    }

    public String processY(String material) {
        return packaging(material) + "產(chǎn)品Y";
    }

}

場景類Client中,對代碼進(jìn)行調(diào)用,如下:

public class Client {

    public static void main(String[] args) {
        Factory factory = new Factory();
        System.out.println(factory.processX("原料"));
        System.out.println(factory.processY("原料"));
    }

}

運行結(jié)果如下:

*原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>產(chǎn)品Y

縱向細(xì)分
因業(yè)務(wù)拓展,工廠除了生產(chǎn)產(chǎn)品X,還生產(chǎn)半成品,簡單包裝一下就可以了,不需要貼上產(chǎn)品X的商標(biāo)。

因為有現(xiàn)有的預(yù)處理,加工,包裝方法,為了方便起見,直接將類Factory中的packaging方法,有private改為public,然后由不同的場景類調(diào)用,代碼如下:


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) + "產(chǎn)品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后,結(jié)果如下:

*原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>

這樣之前的問題又出現(xiàn)了,如果優(yōu)化產(chǎn)品X的生產(chǎn)方案,需要改變原料預(yù)處理的方式,同樣也會牽連到其他生產(chǎn)過程的變化,無論是縱向的還是橫向的。
所以,如果更改了原料預(yù)處理的方法,牽一發(fā)動全身的問題依然存在。

對于這個問題該怎么解決呢?

有很多人為了省事方便可能會采用下面的方法

對于橫向細(xì)分的情況,可以用CP大法將類Factory進(jìn)行拆分,分成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) + "產(chǎn)品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) + "產(chǎn)品Y";
    }

}

縱向細(xì)分的情況可以再拆出一個類

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) + "包裝——>";
    }
}

這樣雖然解決了因職責(zé)橫向細(xì)分或縱向細(xì)分導(dǎo)致的牽一發(fā)動全身的問題,但是這樣就使代碼完全失去復(fù)用性了,而且FactoryX,FactoryY的職責(zé)真正單一了嗎?
從橫向上看,F(xiàn)actoryX只生產(chǎn)產(chǎn)品X,F(xiàn)actoryY只生產(chǎn)產(chǎn)品Y來看,類的職責(zé)是單一了,
但是從縱向上看,兩個類都有顆粒度更小的四個職責(zé),原料預(yù)處理,加工,包裝,產(chǎn)出成品,
SubFactory也有顆粒度更小的三個職責(zé),原料預(yù)處理,加工,包裝。

單一職責(zé)要求我們在編寫類,抽象類,接口時,要使其功能職責(zé)單一純碎,將導(dǎo)致其變更的因素縮減到最少。
按照這個原則對于工廠類Factory我們重新調(diào)整一下實現(xiàn)方案
首先,將四個職責(zé)抽取成以下四個接口


//預(yù)處理接口
public interface IPreProcess {
    String preProcess(String material);
}


//加工接口
public interface IProcess {
    String process(String material);
}


//包裝接口
public interface IPackaging {
    String packaging(String semiProduct);
}


//產(chǎn)出成品接口
public interface IFactory {
    String process(String product);
}

然后,有四個職責(zé)類分別實現(xiàn)這四個接口,如下


//原料預(yù)處理類
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) + "包裝——>";
    }

}
//產(chǎn)出成品類
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)+ "產(chǎn)品X";
    }

}

場景類中調(diào)用代碼如下:


public class Client {

    public static void main(String[] args) {
        IFactory factory = new FactoryX(new Packaging(new Process(new PreProcess())));
        System.out.println(factory.process("原料"));
    }
}

如果增加產(chǎn)品Y的生產(chǎn)(產(chǎn)品Y與產(chǎn)品X除了包裝不同之外,其它都一樣)

可以增加一個IFactory的實現(xiàn)類FactoryY

//產(chǎn)出成品類
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)+ "產(chǎn)品Y";
    }

}

場景類中調(diào)用如下:

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("原料"));
    }
}

運行結(jié)果如下:

*原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>產(chǎn)品Y

如果因市場需求,優(yōu)化產(chǎn)品X的生產(chǎn)方案,改變原料預(yù)處理的方式

 "*" + material + "——>";

改成

 "#" + material + "——>";

可以增加一個原料預(yù)處理接口IPreProcess的實現(xiàn)類PreProcessA,如下


//原料預(yù)處理類
public class PreProcessA implements IPreProcess{

    @Override
    public String preProcess(String material) {
        return "#" + material + "——>";
    }

}

場景類更改如下(不仔細(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("原料"));
    }
}

運行結(jié)果如下:

#原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>產(chǎn)品Y

可以看到,在優(yōu)化產(chǎn)品X的生產(chǎn)方案,改變原料預(yù)處理的方式的過程中,并沒有改變產(chǎn)品Y的正常生產(chǎn)。

如果想生產(chǎn)產(chǎn)品X的半成品的話,不需更改生產(chǎn)代碼,只需在場景類中直接調(diào)用即可,如下

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("原料"));
    }
}

運行結(jié)果如下:

#原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>產(chǎn)品Y
#原料——>加工——>包裝——>

即使哪天市場需求再變化,再優(yōu)化產(chǎn)品X的生產(chǎn)方案,改變原料預(yù)處理的方式

只需再添加個類,實現(xiàn)預(yù)處理接口IPreProcess即可,如下

//原料預(yù)處理類
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("原料"));
    }
}

運行結(jié)果如下:

%原料——>加工——>包裝——>產(chǎn)品X
*原料——>加工——>包裝——>產(chǎn)品Y
#原料——>加工——>包裝——>

依然不會對其他邏輯造成影響

單一職責(zé)原則不是單單的將類的功能進(jìn)行顆?;鸱?,拆分的越細(xì)越好,這樣雖然可以保證類的功能職責(zé)的單一性,但是也會導(dǎo)致類的數(shù)量暴增,功能實現(xiàn)復(fù)雜,一個功能需要多個功能細(xì)分后的類來完成,會造成類的調(diào)用繁瑣,類間關(guān)系交織混亂,后期維護(hù)困難。所以單一職責(zé)原則并不是要求類的功能拆分的越細(xì)越好,對類的功能細(xì)分需要有個度,細(xì)分到什么程度才是最合適呢,細(xì)分到在應(yīng)對未來的拓展時,有且僅有一個因素導(dǎo)致其變更。

在一個項目中,類不是孤立存在的,是需要與其他類相互配合實現(xiàn)復(fù)雜功能的。所以類的職責(zé)和變更因素都是難以衡量的,會因所屬項目的不同而不同。需要在長期的開發(fā)經(jīng)驗中慢慢摸索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容