如需下載源碼掖肋,請訪問
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)驗中慢慢摸索州胳。