設計模式的分類
整體來講設計模式分為3大類:
-
創(chuàng)建型模式,共5種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
-
結構型模式,共7種:適配器模式、裝潢器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
-
行動型模式,共101種:策略模式、模板方法模式、視察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。
6大原則
單1職責原則Single Responsibility Principle
定義:1個類或1個接口,最好只負責1項職責。
問題由來:類T負責兩個不同的職責P1和P2。由于職責P1需要產生改變而需要修改T類時,有可能致使原來運行正常的職責P2功能產生故障。
解決方法:遵守單1職責原則。分別建立兩個類T1和T2,使類T1負責職責P1,類T2負責職責P2。這樣,當修改類T1也不會影響職責P2;同理,當修改類T2時不會影響職責P1。
有時候也會有背背這1原則的代碼存在。由于有職責分散,就是由于某種緣由,職責P被分化為粒度更細的職責P1和P2。比如:類T只負責1個職責P,這樣設計是符合單1職責原則的。后來由于某種緣由,或許是需求變更了,或許是程序的設計者境地提高了,需要將職責P細分為粒度更細的職責P1,P2,這時候如果要使程序遵守單1職責原則,需要將類T也分解為兩個類T1和T2,分別負責P1、P2兩個職責。但是在程序已寫好的情況下,這樣做簡直太費時間了。所以,簡單的修改類T,用它來負責兩個職責是1個比較不錯的選擇,雖然這樣做有悖于單1職責原則。這樣做的風險在于職責分散的不肯定性,由于我們不會想到這個職責P,在未來可能會分散為P1,P2,P3,P4,Pn。所以記住,在職責分散到我們沒法控制的程度之前,立刻對代碼進行重構。
舉例說明,用1個類描寫動物呼吸這個場景:
public class SingleResponsibilityPrinciple { public static void main(String[] args) {
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("豬");
}
}
class Animal { public void breathe(String animal) {
System.out.println(animal + "呼吸空氣");
}
}
程序上線后,發(fā)現(xiàn)問題了,其實不是所有的動物都呼吸空氣的,比如魚就是呼吸水的。修改時如果遵守單1職責原則,需要Animal類細分為陸生動物類Terrestrial,水生動物Aquatic,代碼以下:
public class SingleResponsibilityPrinciple { public static void main(String[] args) {
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe("牛");
terrestrial.breathe("羊");
terrestrial.breathe("豬");
Aquatic aquatic = new Aquatic();
aquatic.breathe("魚");
}
}
class Terrestrial { public void breathe(String animal) {
System.out.println(animal + "呼吸空氣");
}
}
class Aquatic { public void breathe(String animal) {
System.out.println(animal + "呼吸水");
}
}
我們會發(fā)現(xiàn)如果這樣修改花消是很大的,除將原來的類分解以外,還需要修改客戶端。而直接修改類Animal來達成目的雖然背背了單1職責原則,但花消卻小的多,代碼以下:
public class SingleResponsibilityPrinciple { public static void main(String[] args) {
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("豬");
animal.breathe("魚");
}
}
class Animal { public void breathe(String animal) { if ("魚".equals(animal)) {
System.out.println(animal + "呼吸水");
} else {
System.out.println(animal + "呼吸空氣");
}
}
}
可以看到,這類修改方式要簡單的多。但是卻存在著隱患:有1天需要將魚分為呼吸淡水的魚和呼吸海水的魚,則又需要修改Animal類的breathe方法,而對原有代碼的修改會對調用豬,牛,羊等相干功能帶來風險,或許某1天你會發(fā)現(xiàn)程序運行的結果變成牛呼吸水了。
這類修改方式直接在代碼級別上背背了單1職責原則,雖然修改起來最簡單,但隱患卻是最大的。還有1種修改方式:
public class SingleResponsibilityPrinciple { public static void main(String[] args) {
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("豬");
animal.breathe2("魚");
}
}
class Animal { public void breathe(String animal) {
System.out.println(animal + "呼吸空氣");
} public void breathe2(String animal) {
System.out.println(animal + "呼吸水");
}
}
可以看到,這類修改方式?jīng)]有改動原來的方法,而是在類中新加了1個方法,這樣雖然也背背了單1職責原則,但在方法級別上卻是符合單1職責原則的,由于它并沒有動原來方法的代碼。這3種方式各有優(yōu)缺點,那末在實際編程中,需要根據(jù)實際情況來肯定。我的原則是:只有邏輯足夠簡單,才可以在代碼級別上違背單1職責原則;只有類中方法數(shù)量足夠少,才可以在方法級別上違背單1職責原則;
遵守單1職責原的優(yōu)點有:類的復雜性將下降,簡單明細的代碼將使可讀性將大大提高,自但是然可保護性亦將同步提高。變更引發(fā)的風險下降,變更是必定的,如果單1職責原則遵照的好,當修改1個功能時,可以顯著下降對其他功能的影響。
里氏替換原則Liskov Substitution Principle
肯定有很多人跟我剛看到這項原則的時候1樣,對這個原則的名字充滿疑惑。其實緣由就是這項原則最早是在1988年,由麻省理工學院的1位姓里的女士(Barbara Liskov)提出來的。
里氏替換原則的核心精神是:在使用基類的的地方可以任意使用其子類,能保證子類完善替換基類;這1精神實際上是對繼承機制束縛規(guī)范的體現(xiàn)。在父類和子類的具體實現(xiàn)中,嚴格控制繼承層次中的關系特點,以保證用子類替換基類時,程序行動不產生問題,且能正常進行下去。
里氏替換原則主要發(fā)力點是繼承基礎上的抽象和多態(tài),具體就是子類必須實現(xiàn)父類的方法,是重寫;這里要注意重寫(Override)與重載(Overload)的辨別,即便參數(shù)的數(shù)據(jù)范圍產生變化,也能將重寫變成重載!而你本來只是想把所繼承的方法完善的具體點兒!如果是這樣的話絕對會引發(fā)以后業(yè)務邏輯的混亂。
里氏替換原則是關于繼承機制的設計原則,違背里氏替換原則將會使繼承變的1塌胡涂;而遵守里氏替換原則能夠保證系統(tǒng)具有良好的的拓展性,我們可以隨時根據(jù)需要增改不同的子類,這將大大增強程序的硬朗性,讓版本的升級可以做到非常好的兼容;同時基于多態(tài)的抽象機制,能夠很好的減少代碼冗余,避免運行期的類型辨別等;而在項目的實行中不同的子類對應著不同的業(yè)務,使用父類做參數(shù),不同子類可以輪番上陣,必定強大!
定義2:所有援用基類的地方必須能透明地使用其子類的對象。
問題由來:有1功能P1,由類A完成。現(xiàn)需要將功能P1進行擴大,擴大后的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時,有可能會致使原有功能P1產生故障。
解決方案:當使用繼承時,遵守里氏替換原則。類B繼承類A時,除添加新的方法完成新增功能P2外,盡可能不要重寫父類A的方法,也盡可能不要重載父類A的方法。
繼承包括這樣1層含義:父類中凡是已實現(xiàn)好的方法(相對抽象方法而言),實際上是在設定1系列的規(guī)范和契約,雖然它不強迫要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會對全部繼承體系造成破壞。而里氏替換原則就是表達了這1層含義。
繼承作為面向對象3大特性之1,在給程序設計帶來巨大便利的同時,也帶來了弊端。比如使用繼承會給程序帶來侵入性,程序的可移植性下降,增加了對象間的耦合性,如果1個類被其他的類所繼承,則當這個類需要修改時,必須斟酌到所有的子類,并且父類修改后,所有觸及到子類的功能都有可能會產生故障。
舉例說明繼承的風險,我們需要完成1個兩數(shù)相減的功能,由類A來負責。
public class LiskovSubstitutionPrinciple { public static void main(String[] args) {
A a = new A();
System.out.println("100⑸0=" + a.func1(100, 50));
System.out.println("100⑻0=" + a.func1(100, 80));
}
}
class A { public int func1(int a, int b) { return a - b;
}
}
后來,我們需要增加1個新的功能:完成兩數(shù)相加,然后再與100求和,由類B來負責。即類B需要完成兩個功能:
public class LiskovSubstitutionPrinciple { public static void main(String[] args) {
B b = new B();
System.out.println("100⑸0=" + b.func1(100, 50));
System.out.println("100⑻0=" + b.func1(100, 80));
System.out.println("100+20+100=" + b.func2(100, 20));
}
} class A { public int func1(int a, int b) { return a - b;
}
} class B extends A { @Override public int func1(int a, int b) { return a + b;
} public int func2(int a, int b) { return func1(a, b) + 100;
}
}
我們發(fā)現(xiàn)本來運行正常的相減功能產生了毛病。緣由就是類B在給方法起名時無意中重寫了父類的方法,造成所有運行相減功能的代碼全部調用了類B重寫后的方法,造成本來運行正常的功能出現(xiàn)了毛病。在本例中,援用基類A完成的功能,換成子類B以后,產生了異常。在實際編程中,我們常常會通太重寫父類的方法來完成新的功能,這樣寫起來雖然簡單,但是全部繼承體系的可復用性會比較差,特別是應用多態(tài)比較頻繁時,程序運行出錯的概率非常大。如果非要重寫父類的方法,比較通用的做法是:原來的父類和子類都繼承1個更通俗的基類,原本的繼承關系去掉,采取依賴、聚合,組合等關系代替。
里氏替換原則通俗的來說就是:子類可以擴大父類的功能,但不能改變父類原本的功能。它包括以下4層含義:
-
子類可以實現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法。
-
子類中可以增加自己獨有的方法。
-
當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松。
-
當子類的方法實現(xiàn)父類的抽象方法時,方法的后置條件(即方法的返回值)要比父類更嚴格。
看上去很不可思議,由于我們會發(fā)現(xiàn)在自己編程中常常會違背里氏替換原則,程序照樣跑的好好的。所以大家都會產生這樣的疑問,假設我非要不遵守里氏替換原則,你寫的代碼出問題的概率將會大大增加。
依賴顛倒原則Dependence Inversion Principle
定義:高層模塊不應當依賴低層模塊,2者都應當依賴其抽象;抽象不應當依賴細節(jié);細節(jié)應當依賴抽象。其核心思想是:依賴于抽象。
問題由來:類A直接依賴類B,假設要將類A改成依賴類C,則必須通過修改類A的代碼來達成。這類場景下,類A1般是高層模塊,負責復雜的業(yè)務邏輯;類B和類C是低層模塊,負責基本的原子操作;假設修改類A,會給程序帶來沒必要要的風險。
解決方案:將類A修改成依賴接口I,類B和類C各自實現(xiàn)接口I,類A通過接口I間接與類B或類C產生聯(lián)系,則會大大下降修改類A的概率。
依賴顛倒原則基于這樣1個事實:相對細節(jié)的多變性,抽象的東西要穩(wěn)定的多。以抽象為基礎搭建起來的架構比以細節(jié)為基礎搭建起來的架構要穩(wěn)定的多。
在java中,抽象指的是接口或抽象類,細節(jié)就是具體的實現(xiàn)類,使用接口或抽象類的目的是制定好規(guī)范和契約,而不去觸及任何具體的操作,把展現(xiàn)細節(jié)的任務交給他們的實現(xiàn)類去完成。
依賴顛倒原則的核心思想是面向接口編程,我們照舊用1個例子來講明面向接口編程比相對面向實現(xiàn)編程好在甚么地方。場景是這樣的,母親給孩子講故事,只要給她1本書,她就能夠照著書給孩子講故事了。代碼以下:
public class DependenceInversionPrinciple { public static void main(String[] args) {
Mother mother = new Mother();
mother.narrate(new Book());
}
}
class Book { public String getContent() { return "很久很久之前有1個阿拉伯的故事……";
}
}
class Mother { public void narrate(Book book) {
System.out.println("媽媽開始講故事");
System.out.println(book.getContent());
}
}
運行良好,假設有1天,需求變成這樣:不是給書而是給1份報紙,讓這位母親講1下報紙上的故事,報紙的代碼以下:
class Newspaper{ public String getContent(){ return "林書豪38+7領導尼克斯擊敗湖人……";
}
}
這位母親卻辦不到,由于她竟然不會讀報紙上的故事,這太荒唐了,只是將書換成報紙,竟然必須要修改Mother才能讀。假設以后需求換成雜志呢?換成網(wǎng)頁呢?還要不斷地修改Mother,這明顯不是好的設計。緣由就是Mother與Book之間的耦合性太高了,必須下降他們之間的耦合度才行。我們引入1個抽象的接口IReader。讀物,只要是帶字的都屬于讀物:
public class DependenceInversionPrinciple { public static void main(String[] args) {
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
} interface IReader { public String getContent();
} class Newspaper implements IReader { public String getContent() { return "林書豪17+9助尼克斯擊敗老鷹……";
}
} class Book implements IReader { public String getContent() { return "很久很久之前有1個阿拉伯的故事……";
}
} class Mother { public void narrate(IReader reader) {
System.out.println("媽媽開始講故事");
System.out.println(reader.getContent());
}
}
這樣修改后,不管以后怎樣擴大IReader類,都不需要再修改Mother類了。這只是1個簡單的例子,實際情況中,代表高層模塊的Mother類將負責完成主要的業(yè)務邏輯,1旦需要對它進行修改,引入毛病的風險極大。所以遵守依賴顛倒原則可以下降類之間的耦合性,提高系統(tǒng)的穩(wěn)定性,下降修改程序釀成的風險。
采取依賴顛倒原則給多人并行開發(fā)帶來了極大的便利,比如上例中,本來Mother類與Book類直接耦合時,Mother類必須等Book類編碼完成后才可以進行編碼,由于Mother類依賴于Book類。修改后的程序則可以同時開工,互不影響,由于Mother與Book類1點關系也沒有。參與協(xié)作開發(fā)的人越多、項目越龐大,采取依賴致使原則的意義就越重大。
傳遞依賴關系有3種方式,以上的例子中使用的方法是接口傳遞,另外還有兩種傳遞方式:構造方法傳遞和setter方法傳遞,相信譽過Spring框架的,對依賴的傳遞方式1定不會陌生。
在實際編程中,我們1般需要做到以下3點:
-
低層模塊盡可能都要有抽象類或接口,或二者都有。
-
變量的聲明類型盡可能是抽象類或接口。
-
使用繼承時遵守里氏替換原則。
依賴顛倒原則的核心就是要我們面向接口編程,理解了面向接口編程,也就理解了依賴顛倒。
接口隔離原則Interface Segregation Principle
定義:客戶端不應當依賴它不需要的接口;1個類對另外一個類的依賴應當建立在最小的接口上。 否則將會造成接口污染。類A通過接口I依賴類B,類C通過接口I依賴類D,如果接口I對類A和類B來講不是最小接口,則類B和類D必須去實現(xiàn)他們不需要的方法。
解決方案:將臃腫的接口I拆分為獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關系。也就是采取接口隔離原則。
舉例來講明接口隔離原則:
public class InterfaceSegregationPrinciple { public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
} interface I { public void method1(); public void method2(); public void method3(); public void method4(); public void method5();
}
class A { public void depend1(I i) {
i.method1();
} public void depend2(I i) {
i.method2();
} public void depend3(I i) {
i.method3();
}
}
class B implements I { public void method1() {
System.out.println("類B實現(xiàn)接口I的方法1");
} public void method2() {
System.out.println("類B實現(xiàn)接口I的方法2");
} public void method3() {
System.out.println("類B實現(xiàn)接口I的方法3");
} public void method4() {
} public void method5() {
}
}
class C { public void depend1(I i) {
i.method1();
} public void depend2(I i) {
i.method4();
} public void depend3(I i) {
i.method5();
}
}
class D implements I { public void method1() {
System.out.println("類D實現(xiàn)接口I的方法1");
} public void method2() {
} public void method3() {
} public void method4() {
System.out.println("類D實現(xiàn)接口I的方法4");
} public void method5() {
System.out.println("類D實現(xiàn)接口I的方法5");
}
}
可以看到,如果接口過于臃腫,只要接口中出現(xiàn)的方法,不管對依賴于它的類有無用途,實現(xiàn)類中都必須去實現(xiàn)這些方法,這明顯不是好的設計。如果將這個設計修改成符合接口隔離原則,就必須對接口I進行拆分。在這里我們將原本的接口I拆分為3個接口,代碼以下:
public class InterfaceSegregationPrinciple { public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
} interface I1 { public void method1();
} interface I2 { public void method2(); public void method3();
} interface I3 { public void method4(); public void method5();
}
class A { public void depend1(I1 i) {
i.method1();
} public void depend2(I2 i) {
i.method2();
} public void depend3(I2 i) {
i.method3();
}
}
class B implements I1, I2 { public void method1() {
System.out.println("類B實現(xiàn)接口I1的方法1");
} public void method2() {
System.out.println("類B實現(xiàn)接口I2的方法2");
} public void method3() {
System.out.println("類B實現(xiàn)接口I2的方法3");
}
}
class C { public void depend1(I1 i) {
i.method1();
} public void depend2(I3 i) {
i.method4();
} public void depend3(I3 i) {
i.method5();
}
}
class D implements I1, I3 { public void method1() {
System.out.println("類D實現(xiàn)接口I1的方法1");
} public void method4() {
System.out.println("類D實現(xiàn)接口I3的方法4");
} public void method5() {
System.out.println("類D實現(xiàn)接口I3的方法5");
}
}
接口隔離原則的含義是:建立單1接口,不要建立龐大臃腫的接口,盡可能細化接口,接口中的方法盡可能少。也就是說,我們要為各個類建立專用的接口,而不要試圖去建立1個很龐大的接口供所有依賴它的類去調用。本文例子中,將1個龐大的接口變更加3個專用的接口所采取的就是接口隔離原則。在程序設計中,依賴幾個專用的接口要比依賴1個綜合的接口更靈活。接口是設計時對外部設定的契約,通過分散定義多個接口,可以預防外來變更的分散,提高系統(tǒng)的靈活性和可保護性。
說到這里,很多人會覺的接口隔離原則跟之前的單1職責原則很類似,其實不然。其1,單1職責原則原重視的是職責;而接口隔離原則重視對接口依賴的隔離。其2,單1職責原則主要是束縛類,其次才是接口和方法,它針對的是程序中的實現(xiàn)和細節(jié);而接口隔離原則主要束縛接口,主要針對抽象,針對程序整體框架的構建。
采取接口隔離原則對接口進行束縛時,要注意以下幾點:
接口盡可能小,但是要有限度。對接口進行細化可以提高程序設計靈活性是不掙的事實,但是如果太小,則會造成接口數(shù)量過量,使設計復雜化。所以1定要適度。為依賴接口的類定制服務,只暴露給調用的類它需要的方法,它不需要的方法則隱藏起來。只有專注地為1個模塊提供定制服務,才能建立最小的依賴關系。提高內聚,減少對外交互。使接口用最少的方法去完成最多的事情。
應用接口隔離原則,1定要適度,接口設計的過大或太小都不好。設計接口的時候,只有多花些時間去思考和籌劃,才能準確地實踐這1原則。
現(xiàn)實中,如何掌控接口越小越好,這個度很難界定,顆粒度小固然靈活,但同時會造成結構的復雜化,以下有幾個掌控規(guī)則可以參考:
-
1個接口只服務于1個子模塊或業(yè)務邏輯,服務定制;
-
通過業(yè)務邏輯緊縮接口中的public方法,讓接口看起來精悍;
-
已被污染了的接口,盡可能修改,如果變更風險太大,則用適配器模式進行轉化處理;
-
根據(jù)具體的業(yè)務,深入了解邏輯,用心感知去控制設計思路。
具體如何實行接口隔離,主要有兩種方法:
1. 拜托分離,通過增加1個新的接口類型來拜托客戶的要求,隔離客戶和接口的直接依賴,注意這同時也會增加系統(tǒng)的開消;
2. 多重繼承分離,通過接口的多重繼承來實現(xiàn)客戶的需求,這類方式相對較好。具體的使用,視情況而定。
迪米特法則Demeter Principle
定義:1個對象應當對其他對象保持最少的了解。其核心精神是:不和陌生人說話,通俗之意是1個對象對自己需要耦合關聯(lián)調用的類應當知道的更少。這樣會致使類之間的耦合度下降,每一個類都盡可能減少對其他類的依賴,因此,這也很容易使得系統(tǒng)的功能模塊相互獨立,之間不存在很強的依賴關系。
問題由來:類與類之間的關系越密切,耦合度越大,當1個類產生改變時,對另外一個類的影響也越大。
解決方案:盡可能下降類與類之間的耦合。
自從我們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內聚。不管是面向進程編程還是面向對象編程,只有使各個模塊之間的耦合盡可能的低,才能提高代碼的復用率。低耦合的優(yōu)點不言而喻,但是怎樣樣編程才能做到低耦合呢?那正是迪米特法則要去完成的。迪米特法則又叫最少知道原則,最早是在1987年由美國Northeastern University的Ian Holland提出。通俗的來說,就是1個類對自己依賴的類知道的越少越好。也就是說,對被依賴的類來講,不管邏輯多么復雜,都盡可能地的將邏輯封裝在類的內部,對外除提供的public方法,不對外泄漏任何信息。迪米特法則還有1個更簡單的定義:只與直接的朋友通訊。首先來解釋1下甚么是直接的朋友:每一個對象都會與其他對象有耦合關系,只要兩個對象之間有耦合關系,我們就說這兩個對象之間是朋友關系。耦合的方式很多,依賴、關聯(lián)、組合、聚合等。其中,我們稱出現(xiàn)成員變量、方法參數(shù)、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作為局部變量的情勢出現(xiàn)在類的內部。
舉1個例子:有1個團體公司,下屬單位有分公司和直屬部門,現(xiàn)在要求打印出所有下屬單位的員工ID。先來看1下違背迪米特法則的設計。
public class LowOfDemeter { public static void main(String[] args) {
CompanyManager e = new CompanyManager();
e.printAllEmployee(new SubCompanyManager());
}
} class Employee { private String id; public void setId(String id) { this.id = id;
} public String getId() { return id;
}
} class SubEmployee { private String id; public void setId(String id) { this.id = id;
} public String getId() { return id;
}
}
class SubCompanyManager { public ListgetAllEmployee() {
Listlist = new ArrayList(); for (int i = 0; i < 5; i++) {
SubEmployee emp = new SubEmployee(); emp.setId("分公司" + i);
list.add(emp);
} return list;
}
}
class CompanyManager { public ListgetAllEmployee() {
Listlist = new ArrayList(); for (int i = 0; i < 10; i++) {
Employee emp = new Employee(); emp.setId("總公司" + i);
list.add(emp);
} return list;
} public void printAllEmployee(SubCompanyManager sub) {
Listlist1 = sub.getAllEmployee(); for (SubEmployee e : list1) {
System.out.println(e.getId());
}
Listlist2 = this.getAllEmployee(); for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
現(xiàn)在這個設計的主要問題出在CompanyManager中,根據(jù)迪米特法則,只與直接的朋友產生通訊,而SubEmployee類其實不是CompanyManager類的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友),從邏輯上講總公司只與他的分公司耦合就好了,與分公司的員工并沒有任何聯(lián)系,這樣設計明顯是增加了沒必要要的耦合。依照迪米特法則,應當避免類中出現(xiàn)這樣非直接朋友關系的耦合。修改后的代碼以下:
public class LowOfDemeter { public static void main(String[] args) {
CompanyManager e = new CompanyManager();
e.printAllEmployee(new SubCompanyManager());
}
} class Employee { private String id; public void setId(String id) { this.id = id;
} public String getId() { return id;
}
} class SubEmployee { private String id; public void setId(String id) { this.id = id;
} public String getId() { return id;
}
}
class SubCompanyManager { public ListgetAllEmployee() {
Listlist = new ArrayList(); for (int i = 0; i < 5; i++) {
SubEmployee emp = new SubEmployee(); emp.setId("分公司" + i);
list.add(emp);
} return list;
} public void printEmployee() {
Listlist = this.getAllEmployee(); for (SubEmployee e : list) {
System.out.println(e.getId());
}
}
}
class CompanyManager { public ListgetAllEmployee() {
Listlist = new ArrayList(); for (int i = 0; i < 10; i++) {
Employee emp = new Employee(); emp.setId("總公司" + i);
list.add(emp);
} return list;
} public void printAllEmployee(SubCompanyManager sub) {
sub.printEmployee();
Listlist2 = this.getAllEmployee(); for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
修改后,為分公司增加了打印人員ID的方法,總公司直接調用來打印,從而避免了與分公司的員工產生耦合。迪米特法則的初衷是下降類之間的耦合,由于每一個類都減少了沒必要要的依賴,因此的確可以下降耦合關系。但是凡事都有度,雖然可以免與非直接的類通訊,但是要通訊,必定會通過1個”中介”來產生聯(lián)系,例如本例中,總公司就是通過分公司這個”中介”來與分公司的員工產生聯(lián)系的。過分的使用迪米特原則,會產生大量這樣的中介和傳遞類,致使系統(tǒng)復雜度變大。所以在采取迪米特法則時要反復權衡,既做到結構清晰,又要高內聚低耦合。
合成復用原則Composite Reuse Principle
原則是盡可能使用合成/聚合的方式,而不是使用繼承。
開閉原則Open Close Principle
定義:1個軟件實體如類、模塊和函數(shù)應當對擴大開放,對修改關閉。
問題由來:在軟件的生命周期內,由于變化、升級和保護等緣由需要對軟件原有代碼進行修改時,可能會給舊代碼中引入毛病,也可能會使我們不能不對全部功能進行重構,并且需要原有代碼經(jīng)太重新測試。
解決方案:當軟件需要變化時,盡可能通過擴大軟件實體的行動來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化。
開閉原則是面向對象設計中最基礎的設計原則,它指點我們如何建立穩(wěn)定靈活的系統(tǒng)。開閉原則多是設計模式6項原則中定義最模糊的1個了,它只告知我們對擴大開放,對修改關閉,可是到底如何才能做到對擴大開放,對修改關閉,并沒有明確的告知我們。之前,如果有人告知我”你進行設計的時候1定要遵照開閉原則”,我會覺的他甚么都沒說,但貌似又甚么都說了。由于開閉原則真的太虛了。
在仔細思考和仔細瀏覽很多設計模式的文章后,終究對開閉原則有了1點認識。其實,我們遵守設計模式前面5大原則,和使用23種設計模式的目的就是遵守開閉原則。也就是說,只要我們對前面5項原則遵照的好了,設計出的軟件自然是符合開閉原則的,這個開閉原則更像是前面5項原則遵照程度的”平均得分”,前面5項原則遵照的好,平均分自然就高,說明軟件設計開閉原則遵照的好;如果前面5項原則遵照的不好,則說明開閉原則遵照的不好。
其實筆者認為,開閉原則不過就是想表達這樣1層意思:用抽象構建框架,用實現(xiàn)擴大細節(jié)。由于抽象靈活性好,適應性廣,只要抽象的公道,可以基本保持軟件架構的穩(wěn)定。而軟件中易變的細節(jié),我們用從抽象派生的實現(xiàn)類來進行擴大,當軟件需要產生變化時,我們只需要根據(jù)需求重新派生1個實現(xiàn)類來擴大就能夠了。固然條件是我們的抽象要公道,要對需求的變更有前瞻性和預感性才行。
說到這里,再回想1下前面說的5項原則,恰正是告知我們用抽象構建框架,用實現(xiàn)擴大細節(jié)的注意事項而已:
-
單1職責原則告知我們實現(xiàn)類要職責單1;
-
里氏替換原則告知我們不要破壞繼承體系;
-
依賴顛倒原則告知我們要面向接口編程;
-
接口隔離原則告知我們在設計接口的時候要精簡單1;
-
迪米特法則告知我們要下降耦合。
-
而開閉原則是總綱,他告知我們要對擴大開放,對修改關閉。
最后說明1下如何去遵照這6個原則。對這6個原則的遵照其實不是是和否的問題,而是多和少的問題,也就是說,我們1般不會說有無遵照,而是說遵照程度的多少。任何事都是過猶不及,設計模式的6個設計原則也是1樣,制定這6個原則的目的其實不是要我們呆板的遵照他們,而需要根據(jù)實際情況靈活應用。對他們的遵照程度只要在1個公道的范圍內,就算是良好的設計。