設(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⊥宓粒現(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é) 將原料進行預(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可以達到預(yù)期生產(chǎn)要求的同時嘁捷,也導(dǎo)致了產(chǎn)品Y的變化雄嚣,但是產(chǎn)品Y的變化并不在預(yù)期當(dāng)中缓升,這便導(dǎo)致程序運行錯誤甚至崩潰港谊。

為了避免這種問題的發(fā)生,我們在軟件開發(fā)中燥狰,應(yīng)當(dāng)注重各職責(zé)間的解耦和增強功能類的內(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é)原則可以將上面的工廠類按照以下方式進行分解

首先湖笨,定義一個抽象工廠類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é)擴散。
由于軟件的迭代升級诚纸,類的某一職責(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進行拓展暑始,如下:

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中廊镜,對代碼進行調(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進行拆分凛篙,分成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é)原則不是單單的將類的功能進行顆聊唬化拆分霞玄,拆分的越細(xì)越好,這樣雖然可以保證類的功能職責(zé)的單一性拉岁,但是也會導(dǎo)致類的數(shù)量暴增坷剧,功能實現(xiàn)復(fù)雜,一個功能需要多個功能細(xì)分后的類來完成喊暖,會造成類的調(diào)用繁瑣惫企,類間關(guān)系交織混亂,后期維護困難陵叽。所以單一職責(zé)原則并不是要求類的功能拆分的越細(xì)越好狞尔,對類的功能細(xì)分需要有個度,細(xì)分到什么程度才是最合適呢巩掺,細(xì)分到在應(yīng)對未來的拓展時偏序,有且僅有一個因素導(dǎo)致其變更。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逸月,隨后出現(xiàn)的幾起案子栓撞,更是在濱河造成了極大的恐慌,老刑警劉巖碗硬,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓤湘,死亡現(xiàn)場離奇詭異,居然都是意外死亡恩尾,警方通過查閱死者的電腦和手機弛说,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翰意,“玉大人瘸彤,你說我怎么就攤上這事鸵膏。” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵邻寿,是天一觀的道長叹俏。 經(jīng)常有香客問我绽快,道長碱屁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任客年,我火速辦了婚禮霞幅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘量瓜。我一直安慰自己司恳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布绍傲。 她就那樣靜靜地躺著抵赢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唧取。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天划提,我揣著相機與錄音枫弟,去河邊找鬼。 笑死鹏往,一個胖子當(dāng)著我的面吹牛淡诗,可吹牛的內(nèi)容都是我干的骇塘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼韩容,長吁一口氣:“原來是場噩夢啊……” “哼款违!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起群凶,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤插爹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后请梢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赠尾,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年毅弧,在試婚紗的時候發(fā)現(xiàn)自己被綠了气嫁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡够坐,死狀恐怖寸宵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情元咙,我是刑警寧澤梯影,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛾坯,受9級特大地震影響光酣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脉课,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一救军、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧倘零,春花似錦唱遭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袖瞻,卻和暖如春司致,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聋迎。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工脂矫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霉晕。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓庭再,卻偏偏與公主長得像捞奕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拄轻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容