1. 程式人生 > >Java面向對象16種原則

Java面向對象16種原則

避免 design 代碼 war 裏氏替換原則 類圖 系統 依賴倒置原則 調用

一 類的設計原則

1 依賴倒置原則-Dependency Inversion Principle (DIP)

2 裏氏替換原則-Liskov Substitution Principle (LSP)

3 接口分隔原則-Interface Segregation Principle (ISP)

4 單一職責原則-Single Responsibility Principle (SRP)

5 開閉原則-The Open-Closed Principle (OCP)

二 包的設計原則

6 重用發布等價原則-Release ReuseEquivalency Principle (REP)

7 無環依賴原則-The AcyclicDependencies Principle (ADP)

8 穩定依賴原則-The StableDependencies Principle (SDP)

9 穩定抽象等價原則-The StableAbstractions Principle (SAP)

10 共同封閉原則-The CommonClosure Principle (CCP)

11 全部重用原則-The Common Reuse Principle (CRP)

三 擴展原則


12 迪米特法則 -Least Knowledge Principle (LKP)
13 黑盒原則- BBP(Black Box Principle)
14 缺省抽象原則 -DAP(Default Abstraction Principle)
15 接口設計原則 -IDP(Interface Design Principle)
16 不要構造具體的超類原則 -DCSP(Don‘t Concrete SupperclassPrinciple)

1. Dependency Inversion Principle (DIP) - 依賴倒置原則

依賴:在程序設計中,如果一個模塊a使用或調用了另一個模塊b,我們稱模塊a依賴模塊b。
高層模塊與低層模塊:往往在一個應用程序中,我們有一些低層次的類,這些類實現了一些基本的或初級的操作,我們稱之為低層模塊;另外有一些高層次的類,這些類封裝了某些復雜的邏輯,並且依賴於低層次的類,這些類我們稱之為高層模塊。

依賴倒置原則的2個重要方針


A. 高層模塊不應該依賴於低層模塊,二者都應該依賴於抽象
B. 抽象不應該依賴於細節,細節應該依賴於抽象

為什麽叫做依賴倒置(Dependency Inversion)呢?


面向對象程序設計相對於面向過程(結構化)程序設計而言,依賴關系被倒置了。因為傳統的結構化程序設計中,高層模塊總是依賴於低層模塊。

問題的提出:

Robert C. Martin在原文中給出了“Bad Design”的定義:
1. 系統很難改變,因為每個改變都會影響其他很多部分。
2. 當你對某地方做一修改,系統的看似無關的其他部分都不工作了。
3. 系統很難被另外一個應用重用,因為你很難將要重用的部分從系統中分離開來。

導致“Bad Design”的很大原因是“高層模塊”過分依賴“低層模塊”。一個良好的設計應該是系統的每一部分都是可替換的。如果“高層模塊”過分依賴“低層模塊”,一方面一旦“低層模塊”需要替換或者修改,“高層模塊”將受到影響;另一方面,高層模塊很難可以重用。

比如,一個Copy模塊,需要把來自Keyboard的輸入復制到Print,即使對Keyboard和Print的封裝已經做得非常好,但如果Copy模塊裏直接使用Keyboard與Print,Copy任很難被其他應用環境(比如需要輸出到磁盤時)重用。

問題的解決:

為了解決上述問題,Robert C. Martin提出了OO設計的Dependency Inversion Principle (DIP) 原則。
DIP給出了一個解決方案:在高層模塊與低層模塊之間,引入一個抽象接口層。
High Level Classes(高層模塊) --> Abstraction Layer(抽象接口層) --> Low Level Classes(低層模塊)
抽象接口是對低層模塊的抽象,低層模塊繼承或實現該抽象接口。
這樣,高層模塊不直接依賴低層模塊,高層模塊與低層模塊都依賴抽象接口層。
當然,抽象也不依賴低層模塊的實現細節,低層模塊依賴(繼承或實現)抽象定義。

Robert C. Martin給出的DIP方案的類的結構圖:


PolicyLayer-->MechanismInterface(abstract)--MechanismLayer-->UtilityInterface(abstract)--UtilityLayer
類與類之間都通過Abstract Layer來組合關系。

2. Liskov Substitution Principle (LSP) - 裏氏替換原則

所有引用基類的地方必須能透明地使用其子類的對象。也就是說,只有滿足以下2個條件的OO設計才可被認為是滿足了LSP原則:

A 不應該在代碼中出現if/else之類對子類類型進行判斷的條件。以下代碼就違反LSP定義。
if (obj typeof Class1) {
do something
} else if (obj typeof Class2) {
do something else
}
B 子類應當可以替換父類並出現在父類能夠出現的任何地方,或者說如果我們把代碼中使用基類的地方用它的子類所代替,代碼還能正常工作。

裏氏替換原則LSP是使代碼符合開閉原則的一個重要保證。同時LSP體現了:
1) 類的繼承原則:如果一個繼承類的對象可能會在基類出現地方出現運行錯誤,則該子類不應該從該基類繼承,或者說,應該重新設計它們之間的關系。
2)動作正確性保證:從另一個側面上保證了符合LSP設計原則的類的擴展不會給已有的系統引入新的錯誤。

類的繼承原則:
Robert C. Martin舉了Rectangle和Square的例子。這裏沿用這個例子,但用Java語言對其加以重寫,並忽略了某些細節只列出下面的精要部分來說明 裏氏替換原則 對類的繼承上的約束。

  1. class Rectangle {
  2. double width;
  3. double height;
  4. public double getHeight() {
  5. return height;
  6. }
  7. public void setHeight(double height) {
  8. this.height = height;
  9. }
  10. public double getWidth() {
  11. return width;
  12. }
  13. public void setWidth(double width) {
  14. this.width = width;
  15. }
  16. }
  17. class Square extends Rectangle {
  18. public void setHeight(double height) {
  19. super.setHeight(height);
  20. super.setWidth(height);
  21. }
  22. public void setWidth(double width) {
  23. super.setHeight(width);
  24. super.setWidth(width);
  25. }
  26. }

class Rectangle {

double width;

double height;

public double getHeight() {

return height;

}

public void setHeight(double height) {

this.height = height;

}

public double getWidth() {

return width;

}

public void setWidth(double width) {

this.width = width;

}

}

class Square extends Rectangle {

public void setHeight(double height) {

super.setHeight(height);

super.setWidth(height);

}

public void setWidth(double width) {

super.setHeight(width);

super.setWidth(width);

}

}



這裏Rectangle是基類,Square從Rectangle繼承。這種繼承關系有什麽問題嗎?

假如已有的系統中存在以下既有的業務邏輯代碼:
void g(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
if (r.getWidth() * r.getHeight() != 20) {
throw new RuntimeException();
}
}

則對應於擴展類Square,在調用既有業務邏輯時:
Rectangle square = new Square();
g(square);
時會拋出一個RuntimeException異常。這顯然違反了LSP原則。

動作正確性保證:
因為LSP對子類的約束,所以為已存在的類做擴展構造一個新的子類時,根據LSP的定義,不會給已有的系統引入新的錯誤。

Design by Contract

根據Bertrand Meyer提出的Design by Contract(DBC:基於合同的設計)概念的描述,對於類的一個方法,都有一個前提條件以及一個後續條件,前提條件說明方法接受什麽樣的參數數據等,只有前提條件得到滿足時,這個方法才能被調用;同時後續條件用來說明這個方法完成時的狀態,如果一個方法的執行會導致這個方法的後續條件不成立,那麽此方法也不應該正常返回。
現在把前提條件以及後續條件應用到繼承子類中,子類方法應該滿足:
1)前提條件不強於基類.
2)後續條件不弱於基類.
換句話說,通過基類的接口調用一個對象時,用戶只知道基類前提條件以及後續條件。因此繼承類不得要求用戶提供比基類方法要求的更強的前提條件,亦即,繼承類方法必須接受任何基類方法能接受的任何條件(參數)。同樣,繼承類必須順從基類的所有後續條件,亦即,繼承類方法的行為和輸出不得違反由基類建立起來的任何約束,不能讓用戶對繼承類方法的輸出感到困惑。這樣,我們就有了基於合同的LSP,基於合同的LSP是LSP的一種強化。

在很多情況下,在設計初期我們類之間的關系不是很明確,LSP則給了我們一個判斷和設計類之間關系的基準:需不需要繼承,以及怎樣設計繼承關系。

3. Interface Segregation Principle (ISP) - 接口分隔原則


不能強迫用戶去依賴那些他們不使用的接口。換句話說,使用多個專門的接口比使用單一的總接口總要好。它包含了2層意思:
1)接口的設計原則:接口的設計應該遵循最小接口原則,不要把用戶不使用的方法塞進同一個接口裏。
如果一個接口的方法沒有被使用到,則說明該接口過胖,應該將其分割成幾個功能專一的接口。
2)接口的依賴(繼承)原則:如果一個接口a依賴(繼承)另一個接口b,則接口a相當於繼承了接口b的方法,那麽繼承了接口b後的接口a也應該遵循上述原則:不應該包含用戶不使用的方法。 反之,則說明接口a被b給汙染了,應該重新設計它們的關系。

如果用戶被迫依賴他們不使用的接口,當接口發生改變時,他們也不得不跟著改變。換而言之,一個用戶依賴了未使用但被其他用戶使用的接口,當其他用戶修改該接口時,依賴該接口的所有用戶都將受到影響。這顯然違反了開閉原則,也不是我們所期望的。

下面我們舉例說明怎麽設計接口或類之間的關系,使其不違反ISP原則。
假如有一個Door,有lock,unlock功能,另外,可以在Door上安裝一個Alarm而使其具有報警功能。用戶可以選擇一般的Door,也可以選擇具有報警功能的Door。

有以下幾種設計方法:

ISP原則的違反例:


方法一:
在Door接口裏定義所有的方法。圖:

但這樣一來,依賴Door接口的CommonDoor卻不得不實現未使用的alarm()方法。違反了ISP原則。

方法二:
在Alarm接口定義alarm方法,在Door接口定義lock,unlock方法,Door接口繼承Alarm接口。


跟方法一一樣,依賴Door接口的CommonDoor卻不得不實現未使用的alarm()方法。違反了ISP原則。

遵循ISP原則的例:


方法三:通過多重繼承實現

Adapter設計模式的實現。
第2)種方案更具有實用性。
這種設計遵循了ISP設計原則。

方法四:通過委托實現

在Alarm接口定義alarm方法,在Door接口定義lock,unlock方法。接口之間無繼承關系。CommonDoor實現Door接口,
AlarmDoor有2種實現方案:
1)同時實現Door和Alarm接口。
2)繼承CommonDoor,並實現Alarm接口。該方案是繼承方式的

小結

Interface Segregation Principle (ISP)從對接口的使用上為我們對接口抽象的顆粒度建立了判斷基準:在為系統設計接口的時候,使用多個專門的接口代替單一的胖接口。

4. Single Responsibility Principle (SRP) - 單一職責原則

永遠不要讓一個類存在多個改變的理由。換句話說,如果一個類需要改變,改變它的理由永遠只有一個。如果存在多個改變它的理由,就需要重新設計該類。

SRP(Single Responsibility Principle)原則的核心含意是:只能讓一個類有且僅有一個職責。這也是單一職責原則的命名含義。

為什麽一個類不能有多於一個以上的職責呢?
如果一個類具有一個以上的職責,那麽就會有多個不同的原因引起該類變化,而這種變化將影響到該類不同職責的使用者(不同用戶):
1,一方面,如果一個職責使用了外部類庫,則使用另外一個職責的用戶卻也不得不包含這個未被使用的外部類庫。
2,另一方面,某個用戶由於某原因需要修改其中一個職責,另外一個職責的用戶也將受到影響,他將不得不重新編譯和配置。這違反了設計的開閉原則,也不是我們所期望的。

職責的劃分

既然一個類不能有多個職責,那麽怎麽劃分職責呢?
Robert.C Martin給出了一個著名的定義:所謂一個類的一個職責是指引起該類變化的一個原因。如果你能想到一個類存在多個使其改變的原因,那麽這個類就存在多個職責。

Single Responsibility Principle (SRP)的原文裏舉了一個Modem的例子來說明怎麽樣進行職責的劃分,這裏我們也沿用這個例子來說明一下:

SRP違反例:
Modem.java
interface Modem {
public void dial(String pno); //撥號
public void hangup(); //掛斷
public void send(char c); //發送數據
public char recv(); //接收數據
}
咋一看,這是一個沒有任何問題的接口設計。但事實上,這個接口包含了2個職責:第一個是連接管理(dial, hangup);另一個是數據通信(send, recv)。很多情況下,這2個職責沒有任何共通的部分,它們因為不同的理由而改變,被不同部分的程序調用。
所以它違反了SRP原則。

下面的類圖將它的2個不同職責分成2個不同的接口,這樣至少可以讓客戶端應用程序使用具有單一職責的接口:

讓ModemImplementation實現這兩個接口。我們註意到,ModemImplementation又組合了2個職責,這不是我們希望的,但有時這又是必須的。通常由於某些原因,迫使我們不得不綁定多個職責到一個類中,但我們至少可以通過接口的分割來分離應用程序關心的概念。
事實上,這個例子一個更好的設計應該是這樣的,如圖:

小結

Single Responsibility Principle (SRP)從職責(改變理由)的側面上為我們對類(接口)的抽象的顆粒度建立了判斷基準:在為系統設計類(接口)的時候應該保證它們的單一職責性。

5. The Open-Closed Principle (OCP) - 開閉原則

開閉原則(OCP:Open-Closed Principle)是指在進行面向對象設計(OOD:Object Oriented Design)中,設計類或其他程序單位時,應該遵循:
- 對擴展開放(open)
- 對修改關閉(closed)

開閉原則是判斷面向對象設計是否正確的最基本的原理之一。 根據開閉原則,在設計一個軟件系統模塊(類,方法)的時候,應該可以在不修改原有的模塊(修改關閉)的基礎上,能擴展其功能(擴展開放)。
A 擴展開放:某模塊的功能是可擴展的,則該模塊是擴展開放的。軟件系統的功能上的可擴展性要求模塊是擴展開放的。
B 修改關閉:某模塊被其他模塊調用,如果該模塊的源代碼不允許修改,則該模塊修改關閉的。軟件系統的功能上的穩定性,持續性要求是修改關閉的。

這也是系統設計需要遵循開閉原則的原因
1)穩定性。開閉原則要求擴展功能不修改原來的代碼,這可以讓軟件系統在變化中保持穩定。
2)擴展性。開閉原則要求對擴展開放,通過擴展提供新的或改變原有的功能,讓軟件系統具有靈活的可擴展性。
遵循開閉原則的系統設計,可以讓軟件系統可復用,並且易於維護。

開閉原則的實現方法

為了滿足開閉原則的 對修改關閉(closed for modification) 原則以及擴展開放(open for extension) 原則,應該對軟件系統中的不變的部分加以抽象,在面向對象的設計中,
A 可以把這些不變的部分加以抽象成不變的接口,這些不變的接口可以應對未來的擴展;
B 接口的最小功能設計原則。根據這個原則,原有的接口要麽可以應對未來的擴展;不足的部分可以通過定義新的接口來實現;
C 模塊之間的調用通過抽象接口進行,這樣即使實現層發生變化,也無需修改調用方的代碼。

接口可以被復用,但接口的實現卻不一定能被復用。接口是穩定的,關閉的,但接口的實現是可變的,開放的。可以通過對接口的不同實現以及類的繼承行為等為系統增加新的或改變系統原來的功能,實現軟件系統的柔軟擴展。

簡單地說,軟件系統是否有良好的接口(抽象)設計是判斷軟件系統是否滿足開閉原則的一種重要的判斷基準。現在多把開閉原則等同於面向接口的軟件設計。

開閉原則的相對性

軟件系統的構建是一個需要不斷重構的過程,在這個過程中,模塊的功能抽象,模塊與模塊間的關系,都不會從一開始就非常清晰明了,所以構建100%滿足開閉原則的軟件系統是相當困難的,這就是開閉原則的相對性。但在設計過程中,通過對模塊功能的抽象(接口定義),模塊之間的關系的抽象(通過接口調用),抽象與實現的分離(面向接口的程序設計)等,可以盡量接近滿足開閉原則。

6. Release Reuse Equivalency Principle (REP) - 重用發布等價原則


包的內部關系方面(聚合性)的原則,重用粒度等價於發布粒度。重用主要是從用戶的觀點來看的。對用戶來說,使用某個發布單位(組件,類,類群等),如果作者因為某種原因對其作了修改而發布了一個新的版本,用戶會期望在升級為新版本之後,不會影響到原系統的正常運作。
也就是說,對於一個可重用(能供其它用戶或系統使用)的元素(組件,類,類群等),作者應該承諾新版本能夠兼容舊版本。否則,用戶將拒絕使用該元素。

Robert C. Martin給出的對重用的定義:

代碼可以看作為可重用的代碼,當且僅當:
- 它的使用者(下稱用戶)無需看它的源代碼
- 用戶只需聯結靜態庫或包含動態庫
- 當庫發生改變(錯誤糾正,功能增強)時,用戶只需要得到一個新的版本便能集成到原有的系統

怎麽做到重用呢?
一個組件要做到能夠重用,它必須有一個得到良好設計的結構,它所包含所有元素必須也是可以重用的。
因為如果一個為重用而設計的發布單位裏,包含了不可重用的元素,當不可重用的元素發生改變時,用戶也不得不改變原有系統以適應新的版本。這顯然違反了重用的定義規則。
也就是說,一個為重用目的而設計的發布單位裏,不能包含不可重用的元素;如果包含了不可重用的元素,它將變得不可重用。

發布單位

當用戶使用的重用組件被作者修改後,用戶希望得到通知,然後決定是否升級或升級的時機。為了能使升級後的系統也能正常運作,用戶也希望作者有一個規範的發布,包括版本號,類庫的說明等等。
一旦用戶決定升級新版本,不管這些修改是否影響到用戶,用戶也不得不包含新版本所包含的所有類庫。

REP規定重用粒度不能小於發布粒度,所有重用元素也必須被一起發布。

發布粒度可以為包(組件)或類等實體,但一個應用往往包含了很多類,所以,具有更大的尺度的包(組件)更加適合作為發布粒度。

重用發布等價原則為我們指明了包的設計方針:一個包中的元素(類)要麽都可重用,要麽都不可重用。

小結

重用發布等價原則(REP)從用戶觀點的角度上為我們規範了包設計的原則:在設計包時,包中應該包含的元素要麽都可以重用,要麽都不可以重用。

7. The Acyclic Dependencies Principle (ADP) - 無環依賴原則

包之間的依賴結構必須是一個直接的無環圖形(DAG)。也就是說,在依賴結構中不允許出現環(循環依賴)。換成另一個說法是: 包間依賴不能是一個環狀形式。包間關系方面(耦合性)的原則

包的依賴
如果一個包A 中的類引用了包B中的類,我們稱包A依賴包B。
“依賴”在具體的程序語言中表現為,如果A依賴B,C/C++語言則在A包的文件/類中通過#include語句包含B包中的文件/類;Java語言則A包的類中通過import語句引入B包中的類。


圖1(包A依賴包B)
虛線表示一種依賴關系,箭頭表示依賴的方向,箭頭所在的一側就是被依賴的包。

包的循環依賴
我們上面討論了並用圖形表示了包之間的依賴關系。如果存在2個或2個以上的包,它們之間的依賴關系圖出現了環狀,我們就稱包之間存在循環依賴關系。
也就是說它們的依賴結構圖根據箭頭的方向形成了一個環狀的閉合圖形。如圖:

圖2:包的循環依賴
如圖:A依賴B,B依賴C,C依賴A,形成了一個環狀依賴。

包的非循環依賴原則

包是一個比較合適的發布粒度,當修改了包中的代碼(類,模塊等)並發布新的版本時,我們需要把該包以及它所依賴的其它包一起發布。發布之後,還需要驗證系統是否能在新發布的版本下正常運作。
如果多個包之間形成了循環依賴,比如如圖2,A依賴B,B依賴C,C依賴A,我們修改了B並需要發布B的一個新的版本,因為B依賴C,所以發布時應該包含C,但C同時又依賴A,所以又應該把A也包含進發布版本裏。也就是說,依賴結構中,出現在環內的所有包都不得不一起發布。它們形成了一個高耦合體,當項目的規模大到一定程度,包的數目變多時,包與包之間的關系便變得錯綜復雜,各種測試也將變得非常困難,常常會因為某個不相關的包中的錯誤而使得測試無法繼續。而發布也變得復雜,需要把所有的包一起發布,無疑增加了發布後的驗證難度。

循環依賴的打破方法

如果包的依賴形成了環狀結構,怎麽樣打破這種循環依賴呢?
有2種方法可以打破這種循環依賴關系:第一種方法是創建新的包,第二種方法是使用DIP(依賴倒置原則)和ISP(接口分隔原則)設計原則。

方法一:創建新的包
比如對於圖2這種依賴結構:


圖2:包的循環依賴

包C要依賴包A,必定A中包含有A,C共通使用的類,把這些共同類抽出來放在一個新的包D裏。這樣就把C依賴A變成了C依賴D以及A依賴D,從而打破了循環依賴關系。如圖:


這樣,包的依賴關系就從A->B->C->A變成了:
A->B->C->D
A->D

方法二:DIP與ISP設計原則
ISP(接口分隔原則)可以剔除美用到的接口。DIP(依賴倒置原則)在類的調用之間引入抽象層。

如圖,,包A依賴包B(因為包A中的類U使用了包B中的類X);反過來,包B又依賴包A(因為包B中的類Y使用了包A中的類V)


包A,包B之間就形成了一種循環依賴。

我們使用DIP設計原則為V抽象一個接口IVforY,並把該接口放在B包中。
這樣就把Y對V的調用轉化為:
V繼承IVforY
Y調用IVforY
如圖:


這樣一來,包B中的類就不依賴任何包A中的類了。

小結

無環依賴原則(ADP)為我們解決包之間的關系耦合問題。在設計包結構時,不能有循環依賴。

8. The Stable Dependencies Principle (SDP) - 穩定依賴原則

一個設計中的包之間的依賴應該朝著穩定的方向進行。一個包只應該依賴那些比自己更穩定的包。換成另一個說法是: 朝著穩定的方向進行依賴。包之間的關系方面(耦合性)的原則。

包的依賴

如果一個包A 中的類引用了包B中的類,我們稱包A依賴包B。
“依賴”在具體的程序語言中表現為,如果A依賴B,C/C++語言則在A包的文件/類中通過#include語句包含B包中的文件/類;Java語言則A包的類中通過import語句引入B包中的類。

圖1(包A依賴包B)
虛線表示一種依賴關系,箭頭表示依賴的方向,箭頭所在的一側就是被依賴的包。

包的穩定依賴原則

包應該依賴比自己更穩定的包。因為如果依賴一個不穩定的包,那麽當這個不穩定的包發生變化時,本身穩定的包也不得不發生變化,變得不穩定了。

所謂穩定,在現實生活中是指一個物體具有穩固不變的屬性使它很難發生變化。應用到軟件概念上,我們認為一個軟件是穩定的,是因為這個軟件很難發生改變,或更確切地說,是不需要發生改變。一個設計良好,能應對各種變化不需要修改的軟件當然是穩定的了,但事實上,往往一個軟件常常需要對應某個事先沒有預測到的用戶需求而不得不發生改變,當這種改變發生時,能把修改控制在最小的範圍之內,並能穩定的工作(包括軟件本身以及依賴它的其它軟件實體等),我們也會認為該軟件是相對穩定的。

怎麽樣讓一個軟件足夠穩定呢?一個確切的方法是,讓大量其它軟件的包依賴它。一個包被很多其他包依賴是非常穩定的,這是因為被依賴的包為了協調其他包必須做很多的工作來對應各種變化(責任的負擔者)。

圖1:穩定的包X
我們認為X是穩定的,因為:
- X被很多其他包依賴。相當於責任擔當著。
- X沒有依賴別的包,它是獨立的。

相反,下面列出了一個非常不穩定的包Y,如圖:


圖2:不穩定的包Y
我們認為Y是不穩定的,因為:
- Y沒有被其他的包所依賴。不是責任的擔當著。
- Y依賴很多別的包。

包的穩定性的判斷原則

可以通過下面的方法來判斷一個包的穩定系數:
Ca:Afferent Coupling。向心耦合。依賴該包(包含的類)的外部包(類)的數目(i.e. incoming dependencies)。
Ce: Efferent Coupling。離心耦合。被該包依賴的外部包的數目(i.e. outgoing dependencies)。
I: Instability。不穩定性。I=Ce/(Ce+Ca)。它的值處於[0,1]之間。
如圖1,X的Ce=0,所以不穩定性I=0,它是穩定的。相反,如圖2,Y的Ce=3,Ca=0,所以它的不穩定性I=1,它是不穩定的。

SDP要求一個包的不穩定性I要大於它所依賴的包的不穩定性。換句話說,沿著依賴的方向,包的不穩定性應該逐漸降低,穩定性應該逐漸升高。

穩定依賴原則(SDP)為我們解決包之間的關系耦合問題。在設計包結構時,包應該只依賴比自己更穩定的包。

9. The Stable AbstractionsPrinciple (SAP) - 穩定抽象等價原則

最穩定的包應該是最抽象的包。不穩定的包應該是具體的包。包的抽象程度跟它的穩定性成正比。穩定的包應該是抽象的包。

包的穩定抽象等價原則


我們在The Stable Dependencies Principle (SDP) - OO設計的穩定依賴原則 一文中談到了包的穩定性:不容易改變的包應該具有更好的穩定性。

一個包的抽象程度越高,它的穩定性就越高。反之,它的穩定性就越低。一個穩定的包必須是抽象的,反之,不穩定的包必須是具體的。

穩定的包的構成
抽象類或接口通過子類繼承擴展行為,這表示抽象類或接口比它們的子類更具有穩定性。總之,為了構成穩定的包,應該提高包內的抽象類或接口的比率;它們的子類可以放在另一個不穩定的包內,該包依賴上述穩定的包,從而遵循了穩定依賴原則(SDP)。

理想的體系結構應該是:
不穩定的(容易改變的)包處於上層

- 它們是具體的包實現
穩定的(不容易改變的)包處於下層
- 不容易改變,但容易擴展
- 接口比實現(具體的運行代碼)在內在特性上更具有穩定性

圖1:遵循穩定依賴原則(SDP)的理想的體系結構

小結

穩定抽象等價原則(SAP)為我們解決包之間的關系耦合問題。在設計包結構時,穩定的包應該是抽象的(由抽象類或接口構成),不穩定的包應該是具體的(由具體的實現類構成)。

10. The Common ClosurePrinciple (CCP) - 共同封閉原則


一個包中所有的類應該對同一種類型的變化關閉。一個變化影響一個包,便影響了包中所有的類。一個更簡短的說法是:一起修改的類,應該組合在一起(同一個包裏)。包的內部關系方面(聚合性)的原則

如果必須修改應用程序裏的代碼,我們希望所有的修改都發生在一個包裏(修改關閉),而不是遍布在很多包裏。
CCP原則就是把因為某個同樣的原因而需要修改的所有類組合進一個包裏。如果2個類從物理上或者從概念上聯系得非常緊密,它們通常一起發生改變,那麽它們應該屬於同一個包。

CCP跟開閉原則(OCP: Open Closed Principle) 有著很深的淵源關系,CCP的“關閉”(closure)就是OCP所提倡的:classes should be closed for modification but open for extension. 類應該對修改關閉,對擴展開放。但我們知道,100%的“關閉”是不現實的,我們在設計系統時,只能盡量地保持對大多數可預見的修改關閉。
CCP延伸了OCP的“關閉”概念,當因為某個原因需要修改時,把需要修改的範圍限制在一個最小範圍內的包裏。CCP原則幫助我們決定哪些類應該被放到同一個包裏。

小結

共同封閉原則(CCP)從軟件功能的角度上為我們規範了包設計的一個原則:在設計包時,相互之間緊密關聯的類應該放在同一包裏。

11. The Common Reuse Principle (CRP) - 全部重用原則

包的所有類被一起重用。如果你重用了其中的一個類,就重用全部。換成另一個比較淺顯易懂的說法:沒有被一起重用的類不應該被組合在一起。CRP原則幫助我們決定哪些類應該被放到同一個包裏。包的內部關系方面(聚合性)的原則

依賴一個包就是依賴這個包所包含的一切。當一個包發生了改變,並發布新的版本,使用這個包的所有用戶都必須在新的包環境下驗證他們的工作,即使被他們使用的部分沒有發生任何改變。
因為如果包中包含有未被使用的類,即使用戶不關心該類是否改變,但用戶還是不得不升級該包並對原來的功能加以重新測試。

“不能強迫用戶去依賴那些他們不使用的接口”,把這個概念應用到更廣範圍的包上,就是CRP的基本理念:不要把用戶不使用的類一起組合進包裏。

CRP與REP一樣,都是從方便用戶重用的角度去設計包,重用者是他們的受益者,CCP則讓系統的維護者受益。CCP讓包盡可能大(CCP原則加入功能相關的類),CRP則讓包盡可能小(CRP原則剔除不使用的類)。它們的出發點不一樣,但不相互沖突。CRP保證了包的內部具有很高的聚合性。

全部重用原則(CRP)從用戶的角度上為我們規範了包設計的一個原則:在設計包時,相互之間沒有緊密關聯的類不應該放在同一包裏。

12. Least Knowledge Principle (LKP) -迪米特法則

迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP)。

一個對象應該對其他對象有最少的了解。通俗地講,一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何復雜都和我沒關系,那是你的事情,我就知道你提供的public方法,我就調用這麽多,其他的一概不關心。

含義:

  • 只和朋友交流

朋友類的定義是這樣的:出現在成員變量、方法的輸入輸出參數中的類稱為成員朋友類,而出現在方法體內部的類不屬於朋友類。下面的代碼在方法體內部依賴了其他類,這嚴重違反迪米特法則

01

public class Teacher {

02

03

public void commond(GroupLeader groupLeader) {

04

List<Girl> listGirls = new ArrayList<Girl>();

05

06

for (int i = 0; i < 20; i++) {

07

listGirls.add(new Girl());

08

}

09

10

groupLeader.countGirls(listGirls);

11

}

12

13

}

方法是類的一個行為,類竟然不知道自己的行為與其他類產生了依賴關系,這是不允許的。

正確的做法是:

1

public class Teacher {

2

3

public void commond(GroupLeader groupLeader) {

4

groupLeader.countGirls();

5

}

6

7

}

01

public class GroupLeader {

02

03

private List<Girl> listGirls;

04

05

public GroupLeader(List<Girl> _listGirls) {

06

this.listGirls = _listGirls;

07

}

08

09

public void countGirls() {

10

System.out.println("女生數量是:" + listGirls.size());

11

}

12

13

}

註意:一個類只和朋友交流,不與陌生類交流,不要出現getA().getB().getC().getD()這種情況(在一種極端情況下允許出現這種訪問,即每一個點號後面的返回類型都相同),類與類之間的關系是建立在類間的,而不是方法間,因此一個方法盡量不引入一個類中不存在的對象,當然,JDK API提供的類除外。

  • 朋友間也是有距離的

一個類公開的public屬性或方法越多,修改時涉及的面也就越大,變更引起的風險擴散也就越大。因此,為了保持朋友類間的距離,在設計時需要反復衡量:是否還可以再減少public方法和屬性,是否可以修改為private、package-private(包類型,在類、方法、變量前不加訪問權限,則默認為包類型)、protected等訪問權限,是否可以加上final關鍵字等。

註意:迪米特法則要求類“羞澀”一點,盡量不要對外公布太多的public方法和非靜態的public變量,盡量內斂,多使用private、package-private、protected等訪問權限。

  • 是自己的就是自己的

如果一個方法放在本類中,既不增加類間關系,也對本類不產生負面影響,就放置在本類中。

  • 謹慎使用Serializable

最後,迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了以後,類的復用率才可以提高。

Principle –縱覽


----類原則 ----
1.單一職責原則 -Single Responsibility Principle(SRP)
就一個類而言,應該僅有一個引起它變化的原因。
(職責即為“變化的原因”。)
2.開放-封閉原則 - OpenClose Principle(OCP)
軟件實體(類、模塊、函數等)應該是可以擴展的,但是不可修改。
(對於擴展是開放的,對於更改是封閉的.
關鍵是抽象.將一個功能的通用部分和實現細節部分清晰的分離開來.
開發人員應該僅僅對程序中呈現出頻繁變化的那些部分作出抽象.
拒絕不成熟的抽象和抽象本身一樣重要. )
3.裏氏替換原則 -Liskov Substitution Principle(LSP)
子類型(subclass)必須能夠替換掉它們的基類型(superclass)。
4.依賴倒置原則(IoCP) 或 依賴註入原則 -Dependence Inversion Principle(DIP)
抽象不應該依賴於細節。細節應該依賴於抽象。
(Hollywood原則: "Don‘t call us, we‘ll call you".
程序中所有的依賴關系都應該終止於抽象類和接口。
針對接口而非實現編程。
任何變量都不應該持有一個指向具體類的指針或引用。
任何類都不應該從具體類派生。
任何方法都不應該覆寫他的任何基類中的已經實現了的方法。)
5.接口隔離原則(ISP)
不應該強迫客戶依賴於它們不用的方法。
接口屬於客戶,不屬於它所在的類層次結構。
(多個面向特定用戶的接口勝於一個通用接口。)


----包內聚原則 ----
6.重用發布等價原則(REP)
重用的粒度就是發布的粒度。
7.共同封閉原則(CCP)
包中的所有類對於同一類性質的變化應該是共同封閉的。
一個變化若對一個包產生影響,
則將對該包中的所有類產生影響,
而對於其他的包不造成任何影響。
8.共同重用原則(CRP)
一個包中的所有類應該是共同重用的。
如果重用了包中的一個類,
那麽就要重用包中的所有類。
(相互之間沒有緊密聯系的類不應該在同一個包中。)
----包耦合原則
9.無環依賴原則(ADP)
在包的依賴關系圖中不允許存在環。
10.穩定依賴原則(SDP)
朝著穩定的方向進行依賴。
應該把封裝系統高層設計的軟件(比如抽象類)放進穩定的包中,
不穩定的包中應該只包含那些很可能會改變的軟件(比如具體類)。
11.穩定抽象原則(SAP)
包的抽象程度應該和其穩定程度一致。
(一個穩定的包應該也是抽象的,一個不穩定的包應該是抽象的. )


----其它擴展原則----
12.BBP(Black Box Principle)黑盒原則
多用類的聚合,少用類的繼承。
13.DAP(Default Abstraction Principle)缺省抽象原則
在接口和實現接口的類之間引入一個抽象類,這個類實現了接口的大部分操作.
14.IDP(Interface Design Principle)接口設計原則
規劃一個接口而不是實現一個接口。
15.DCSP(Don‘t Concrete Supperclass Principle)不要構造具體的超類原則
避免維護具體的超類。
16.迪米特法則
一個類只依賴其觸手可得的類。

Java面向對象16種原則