面向對象設計的基本原則

1、單一職責原則(single responsibility principle )

      There should never be more than one reason for a class to change.

      所謂單一職責原則,就是對一個類而言,應該僅有一個引起它變化的原因。換句話說,一個類的功能要單一,只做與它相關的事情。在類的設計過程中要按職責進行設計,彼此保持正交,互不干涉。

什麼是職責?

      在SRP 中,職責定義爲“變化的原因”。如果你能夠想到多於一個的動機去改變一個類,那麼該類就具有多於一個的職責。

爲什麼要採用單一職責原則?

      因爲每一個職責都是變化的一個軸線,當需求變化時,該變化會反映爲類的職責的變化。如果一個類承擔了多於一個的職責,那麼就意味着引起它的變化的原因會有多個。如果一個類承擔的職責過多,那麼就等同於把這些職責耦合在了一起。一個職責的變化可能會抑制到該類完成其他職責的能力,這樣的耦合會導致脆弱的設計。當變化發生時,設計會受到意想不到的破壞。單一職責原則正是實現高內聚低耦合需要遵守的一個原則。

       注意: 單一職責原則簡單而直觀,但在實際應用中很難實現。只有變化的軸線僅當實際發生時才具有真正的意義。如果沒有預兆,那麼去應用SRP或者其他任何的原則都是不明智的。

下面就Modem接口爲例,說明其原則。

Java代碼  
  1. interface Modem {  
  2.     public void dial(String pno);   //撥號  
  3.     public void hangup();    //掛斷  
  4.     public void send(char c);   //發送數據  
  5.     public char recv();   //接收數據  
  6. }  

      大多數會認爲看起來非常合理,該接口聲明的4個函數確實是調制解調器的功能。 然而,該接口中卻顯示出兩個職責。第一個職責連接管理,第二個職責是數據通信。dial和hangup函數進行調制解調器的連接管理,而send和recv負責進行數據通信。這兩個職責應該被分開嗎?這依賴於應用程序變化的方式。如果應用程序的變化會影響到連接函數的簽名,那麼這個設計就是僵硬的設計,因爲調用send和 recv的類必須重新編譯、部署的次數會超過我們預想的情況。在這種情況下,這兩個職責必須被分離,我們分別實現這兩個職責於:

Java代碼  
  1. interface DataChannel  
  2. {  
  3.     public void send(char c);  
  4.     public void recv();  
  5. }  
  6.   
  7. interface Connection  
  8. {  
  9.     public void dial(string pno);  
  10.     public void hangup();  
  11. }  
下面的類圖將它的2個不同職責分成2個不同的接口,這樣至少可以讓客戶端應用程序使用具有單一職責的接口:

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


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

高內聚、低耦合解釋:

      這是軟件工程中的概念,是判斷設計好壞的標準,主要是面向OO的設計,主要是看類的內聚性是否高,偶合度是否低
      首先要知道一個軟件是由多個子程序組裝而成, 而一個程序由多個模塊(方法)構成!

“高內聚,低耦合”主要是闡述的面向對象系統中,各個類需要職責分離的思想。 
      每一個類完成特定的獨立的功能,這個就是高內聚。耦合就是類之間的互相調用關係,如果耦合很強,互相牽扯調用很多,那麼會牽一髮而動全身,不利於維護和擴展。

       類之間的設置應該要低耦合,但是每個類應該要高內聚.耦合是類之間相互依賴的尺度.如果每個對象都有引用其它所有的對象,那麼就有高耦合,這是不合乎要求的,因爲在兩個對象之間,潛在性地流動了太多信息.低耦合是合乎要求的:它意味着對象彼此之間更獨立的工作.低耦合最小化了修改一個類而導致也要修改其它類的"連鎖反應". 內聚是一個類中變量與方法連接強度的尺度.高內聚是值得要的,因爲它意味着類可以更好地執行一項工作.低內聚是不好的,因爲它表明類中的元素之間很少相關.成分之間相互有關聯的模塊是合乎要求的.每個方法也應該高內聚.大多數的方法只執行一個功能.不要在方法中添加'額外'的指令,這樣會導致方法執行更多的函數。 

      推廣開來說,這個思想並不限於類與類之間的關係。模塊和模塊,子系統之間也都要遵守這個原則,纔可以設計出延展性比較強的系統。  

開閉原則(Open-Closed Principle,OCP)

1、“開-閉”原則的定義及優點

1)定義:一個軟件實體應當對擴展開放,對修改關閉( Software entities should be open for extension,but closed for modification.)。即在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展。

2)滿足“開-閉”原則的系統的優點

      a)通過擴展已有的軟件系統,可以提供新的行爲,以滿足對軟件的新需求,使變化中的軟件系統有一定的適應性和靈活性。

      b)已有的軟件模塊,特別是最重要的抽象層模塊不能再修改,這就使變化中的軟件系統有一定的穩定性和延續性。

      c)這樣的系統同時滿足了可複用性與可維護性。

2、如何實現“開-閉”原則

      在面向對象設計中,不允許更改的是系統的抽象層,而允許擴展的是系統的實現層。換言之,定義一個一勞永逸的抽象設計層,允許儘可能多的行爲在實現層被實現。

      解決問題關鍵在於抽象化,抽象化是面向對象設計的第一個核心本質。

      對一個事物抽象化,實質上是在概括歸納總結它的本質。抽象讓我們抓住最最重要的東西,從更高一層去思考。這降低了思考的複雜度,我們不用同時考慮那麼多的東西。換言之,我們封裝了事物的本質,看不到任何細節。

      在面向對象編程中,通過抽象類及接口,規定了具體類的特徵作爲抽象層,相對穩定,不需更改,從而滿足“對修改關閉”;而從抽象類導出的具體類可以改變系統的行爲,從而滿足“對擴展開放”。

對實體進行擴展時,不必改動軟件的源代碼或者二進制代碼。關鍵在於抽象。

3、對可變性的封裝原則

“開-閉”原則也就是“對可變性的封裝原則”(Principle of Encapsulation of Variation ,EVP)。即找到一個系統的可變因素,將之封裝起來。換言之,在你的設計中什麼可能會發生變化,應使之成爲抽象層而封裝,而不是什麼會導致設計改變才封裝。

      “對可變性的封裝原則”意味着:

       a)一種可變性不應當散落在代碼的許多角落,而應當被封裝到一個對象裏面。同一可變性的不同表象意味着同一個繼承等級結構中的具體子類。因此,此處可以期待繼承關係的出現。繼承是封裝變化的方法,而不僅僅是從一般的對象生成特殊的對象。

       b)一種可變性不應當與另一種可變性混合在一起。作者認爲類圖的繼承結構如果超過兩層,很可能意味着兩種不同的可變性混合在了一起。

       使用“可變性封裝原則”來進行設計可以使系統遵守“開-閉”原則。即使無法百分之百的做到“開-閉”原則,但朝這個方向努力,可以顯著改善一個系統的結構。  

里氏代換原則(Liskov Substitution Principle, LSP

1、里氏代換原則定義

若對於每一個類型S的對象o1,都存在一個類型T的對象o2,使得在所有針對T編寫的程序P中,用o1替換o2後,程序P的行爲功能不變,則ST的子類型。
   What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T. (意思就是程序P滿足以下特性:是針對更高的抽象類型來編程。以上爲例,這個抽象類型指的就是T。)
   
即,一個軟件實體如果使用的是一個基類的話,那麼一定適用於其子類。而且它覺察不出基類對象和子類對象的區別。也就是說,在軟件裏面,把基類都替換成它的子類,程序的行爲沒有變化。反過來的代換不成立,如果一個軟件實體使用的是一個子類的話,那麼它不一定適用於基類。

2、里氏代換原則與-原則的關係

   實現-原則的關鍵步驟是抽象化。基類與子類之間的繼承關係就是抽象化的體現。因此里氏代換原則是對實現抽象化的具體步驟的規範。違反里氏代換原則意味着違反了-原則,反之未必。

3、里氏代換原則的四層含義

1)子類必須完全實現父類的方法。在類中調用其他類是務必要使用父類或接口,如果不能使用父類或接口,則說明類的設計已經違背了LSP原則。

2)子類可以有自己的個性。子類當然可以有自己的行爲和外觀了,也就是方法和屬性

3)覆蓋或實現父類的方法時輸入參數可以被放大。即子類可以重載父類的方法,但輸入參數應比父類方法中的大,這樣在子類代替父類的時候,調用的仍然是父類的方法。即以子類中方法的前置條件必須與超類中被覆蓋的方法的前置條件相同或者更寬鬆。

4)覆蓋或實現父類的方法時輸出結果可以被縮小。

4、里氏代換原則在設計模式中的體現

   策略模式(Strategy

   如果有一組算法,那麼就將算法封裝起來,使得它們可以互換。客戶端依賴於基類類型,而變量的真實類型則是具體策略類。這是具體策略焦色可以即插即用的關鍵。

   合成模式(Composite

   合成模式通過使用樹結構描述整體與部分的關係,從而可以將單純元素與符合元素同等看待。由於單純元素和符合元素都是抽象元素角色的子類,因此兩者都可以替代抽象元素出現在任何地方。里氏代換原則是合成模式能夠成立的基礎。

   代理模式(Proxy

   代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。代理模式能夠成立的關鍵,就在於代理模式與真實主題模式都是抽象主題角色的子類。客戶端只知道抽象主題,而代理主題可以替代抽象主題出現在任何需要的地方,而將真實主題隱藏在幕後。里氏代換原則是代理模式能夠成立的基礎。
5
、總結
  
 里氏代換原則是對開閉原則的擴展,它表明我們在創建基類的新的子類時,不應該改變基類的行爲。也就是不要消弱基類的行爲。

面向對象的設計關注的是對象的行爲,它是使用行爲來對對象進行分類的,只有行爲一致的對象才能抽象出一個類來。我經常說類的繼承關係就是一種“Is-A”關係,實際上指的是行爲上的“Is-A”關係,可以把它描述爲“Act-As” 

 

派生類重寫父類方法可能違反Liskov替換原則?

如果父類僅僅聲明瞭抽象方法,而各派生類分別實現了該方法,那麼就如同實現接口一樣,可達到多態的目的;

如果父類實現了某方法,那麼它對外所描述的行爲也就確定了,派生類如果重寫這個方法,那麼就修改了這個行爲,當派生類實例被父類型的引用使用時,表現出的行爲與父類本身的實例不相符,即違反了Liskov substitution principle。

舉個例子

現實中,正方形是矩形的一種特殊形式。

現在先有Rectangle作爲父類,具有height和width兩個property(有邪惡的getter/setter);再有Square繼承Rectangle做子類,由於Square長與高相等,所以重寫height和width的set方法爲同時設置長與高。至此似乎是設計與現實完全相符,接着有人這樣調用了:

1
2
3
4
5
6
7
8
9
10
11
void tryAreaCalculation(Rectangle rectangle) {
 
     rectangle.setHeight(4);
     rectangle.setWidth(5);
 
     assertEquals(20, rectangle.area());
}
 
....
 
tryAreaCalculation(newSquare());

結果assertion失敗,“expect: 20, but was 25.” 也許我們會說,明知道傳入的是Square對象還寫那樣的assertion是不可能出現的,但不清楚程序的人如果只看tryAreaCalculation方法,就會認爲“矩形面積=長×高=4×5=20“是理所當然的。這顯然不是我們想看到的事。

當然,這個例子並不是Liskov替換原則所針對問題的典型例子,在以“不擇手段地複用代碼”爲目的的繼承例子中(class Students extends List<Student>),大家更能明白Liskov替換原則的意義,以及對組合和繼承的選擇應首先去考慮面向對象的模型。

不要讓OO的眼光被具體編程語言遮擋

是不是覺得我把類繼承妖魔化了呢?有沒有因爲擔心人類被官員類繼承就不敢給人類添加“說話”的方法呢?不必這樣。我們在用面向對象編程時要清楚,什麼是從面向對象建模角度認爲對的事,什麼是所使用的編程語言能夠做到的事。

如果你手上的是Java語言,發現官員會說話,人也會說話,官員又是人的一種,那麼就在人類中實現“說話”這個方法,再讓官員類繼承人類(即便在未來可能會在官員類中重寫說話方法,加入說空話的語句),這一切已是你能做到的合理的事了。但請心裏清楚,這並不是使用OOP對待此模型的最正確方案,因爲Java語言的限制。

如果編程語言有了如Mixin的特性(例如Ruby中的Mixin、Scala中的trait,下面以Ruby爲例),那麼你會發現在這個問題上你能有更好的解決方案。爲了官員、教授等也是人的事實,我們依舊讓這些類繼承人類,但除了固有屬性如身高、性別,以及固有行爲如睡覺,不繼承任何可變的因素;把可能共用的因素都單另放在Module中,之後在需要的類中Mixin進來,如爲程序員、司機、中學生等類Mixin普通“說話”方法所在的Module,而在官員類中定義含說空話邏輯的“說話”方法。

總結下經驗

  • 設計時的考慮,應首先尊重使用面向對象思想的建模,其次參照具體編程語言的特性,第三考慮與已有設計的配合和約定,必要時堅持正確的事去爭取。
  • 用接口描述行爲,各類通過實現接口來定義行爲的具體內容;留意接口隔離(ISP)。
  • 用基類抽象固有屬性和行爲,減少public方法,合理使用Template method模式;子類(儘量)不去重寫繼承自基類的行爲,只添加新屬性和行爲(LSP, OCP)。
  • 用組合描述以借用其他對象的行爲爲目的的設計。

 

類的繼承原則:如果一個繼承類的對象可能會在基類出現的地方出現運行錯誤,則該子類不應該從該基類繼承,或者說,應該重新設計它們之間的關係。

動作正確性保證:符合里氏代換原則的類擴展不會給已有的系統引入新的錯誤。 

接口隔離原則(Interface Segregation Principle)

1、接口隔離原則的定義:

第一種定義: Clients should not be forced to depend upon interfaces that they don't use.客戶端不應該依賴它不需用的接口。

第二種定義:The dependency of one class to another one should depend on the smallest possible interface。一個類對另外一個類的依賴性應當是建立在最小的接口上的。

換句話說,使用多個專門的接口比使用單一的總接口總要好,建立單一接口,不要建立臃腫龐大的接口。一個接口代表一個角色,不應當將不同的角色都交給一個接口。沒有關係的接口合併在一起,形成一個臃腫的大接口,這是對角色和接口的污染。不應當將幾個不同的角色都交給同一個接口,而應當交給不同的接口。

2、接口污染定義:

所謂接口污染就是爲接口添加了不必要的職責。在接口中加一個新方法只是爲了給實現類帶來好處,以減少類的數目。持續這樣做,接口就被不斷污染,變胖。實際上,類的數目根本不是什麼問題,接口污染會帶來維護和重用方面的問題。最常見的問題是我們爲了重用被污染的接口,被迫實現並維護不必要的方法。因此,我們必須分離客戶程序,分離客戶程序就是分離接口。

3、分離接口的實現方法:

分離接口的方式一般分爲兩種:

1) 使用委託分離接口。(Separation through Delegation

就把請求委託給別的接口的實現類來完成需要的職責,就是適配器模式(Adapter)

2) 使用多重繼承分離接口。(Separation through Multiple Inheritance。)

該方法通過實現多個接口來完成需要的職責。

兩種方式各有優缺點,通常我們應該先考慮後一個方案,如果涉及到類型轉換時則選擇前一個方案。

4、實例

假如有一個Door,有lockunlock功能,另外,可以在Door上安裝一個Alarm而使其具有報警功能。用戶可以選擇一般的Door,也可以選擇具有報警功能的Door

要遵循ISP設計原則,方案如下:

1、在IAlarm接口定義alarm方法,在IDoor接口定義lockunlock方法。接口之間無繼承關係。CommonDoor實現IDoor接口。


public interface IDoor {

    public void lock();

    public void unlock();

}

public interface IAlarm {

    public void alarm();

}

public class CommonDoor implements IDoor {

    public void lock() {

       System.out.println("CommonDoor is lock!");

    }

    public void unlock() {

       System.out.println("CommonDoor is unlock!");

    }

}


AlarmDoor2種實現方案:

1)同時實現IDoorIAlarm接口。

public class AlarmDoor implements IDoor, IAlarm {

    public void lock() {

       System.out.println("AlarmDoor is lock!");

    }

    public void unlock() {

       System.out.println("AlarmDoor is unlock!");

    }

    public void alarm() {

       System.out.println("AlarmDoor is alarm!");

    }

}

2)繼承CommonDoor,並實現Alarm接口。該方案是繼承方式的Adapter設計模式的實現。

此種方案更具有實用性。

public class AlarmDoor extends CommonDoor implements IAlarm {

    public void lock() {

       super.lock();

    }

    public void unlock() {

       super.unlock();

    }

    public void alarm() {

       System.out.println("AlarmDoor is alarm!");

    }

}

2、採用委讓實現


public interface IDoor {

    public void lock();

    public void unlock();

}

public interface IAlarm {

public void lock();

public void unlock();

    public void alarm();

}

public class CommonDoor implements IDoor {

    public void lock() {

       System.out.println("CommonDoor is lock!");

    }

    public void unlock() {

       System.out.println("CommonDoor is unlock!");

    }

}


採用委託的方式即採用對象適配器的方式

public class AlarmDoor implements IAlarm {

    private CommonDoor commdoor=new CommonDoor();

    public void lock() {

       commdoor.lock();

    }

    public void unlock() {

       commdoor.unlock();

    }

    public void alarm() {

       System.out.println("AlarmDoor is alarm!");

    }

}

5、小結

如果已經設計成了胖接口,可以使用適配器模式隔離它。像其他設計原則一樣,接口隔離原則需要額外的時間和努力,並且會增加代碼的複雜性,但是可以產生更靈活的設計。如果我們過度的使用它將會產生大量的包含單一方法的接口,所以需要根據經驗並且識別出那些將來需要擴展的代碼來使用它。  

 

依賴倒置原則(Dependence Inversion Principle)

1、依賴倒置原則的定義

     1)上層模塊不應該依賴於底層模塊,它們都應該依賴於抽象。

     2)抽象不應該依賴於細節,細節應該依賴於抽象,要針對接口編程,不要針對實現編程。

      Abstractions should not depend upon details,Details should depend upon abstractions.Program to an interface, not an implementation.

      也就是說應當使用接口和抽象類進行變量類型聲明、參數類型聲明、方法返還類型說明,以及數據類型的轉換等。而不要用具體類進行變量的類型聲明、參數類型聲明、方法返還類型說明,以及數據類型的轉換等。要保證做到這一點,一個具體類應當只實現接口和抽象類中聲明過的方法,而不要給出多餘的方法。

      基於這個原則,設計類結構的方式應該是從上層模塊到底層模塊遵循這樣的結構:上層類--->抽象層--->底層類。

2、依賴倒置原則與開閉原則的關係

“開-閉”原則與依賴倒轉原則是目標和手段的關係。如果說開閉原則是目標,依賴倒轉原則是到達"開閉"原則的手段。如果要達到最好的"開閉"原則,就要儘量的遵守依賴倒轉原則,依賴倒轉原則是對"抽象化"的最好規範。里氏代換原則是依賴倒轉原則的基礎,依賴倒轉原則是里氏代換原則的重要補充。

 3、實例

      下面是一個違反了依賴倒轉原則的例子。我們有一個上層類Manager和底層類Worker。我們需要在程序中添加一個新模塊,因爲有新的特殊的工作者被僱用。爲此,我們創建一個新的類SuperWorker。

      假設Manager是一個包含非常複雜的邏輯的類,現在爲了引入新的SuperWorker,我們需要修改它。讓我們看一下這有哪些缺點:

      (1)我們需要修改Manager類(記住,它是一個非常複雜的類,這需要一些時間和努力)。 
      (2)Manager類的一些現有功能可能會受到影響。 
      (3)需要重做單元測試。 
       所有的這些問題需要大量的時間去解決。但是如果程序的設計符合依賴倒轉原則將會非常簡單。意思是我們設計Manager類和一個IWorker接口以及一些實現了該接口的Worker類。當需要添加SuperWorker類時我們只需要讓它實現IWorker接口。

Java代碼  
  1. //Dependency Inversion Principle - Bad example     
  2. class Worker {     
  3.     public void work() {     
  4.         // ....working     
  5.     }     
  6. }     
  7.     
  8. class Manager {     
  9.     Worker m_worker;     
  10.     
  11.     public void setWorker(Worker w) {     
  12.         m_worker=w;     
  13.     }     
  14.     
  15.     public void manage() {     
  16.         m_worker.work();     
  17.     }     
  18. }     
  19.     
  20. class SuperWorker {     
  21.     public void work() {     
  22.         //.... working much more     
  23.     }     
  24. }    

 

     下面是支持依賴倒轉原則的代碼。在這個新的設計中,我們增加了一個新的抽象層IWork接口。現在,上面的問題得到了解決:

      不需要修改Manager類。

      使對Manager類現有功能的影響最小化。

      不需要對Manager類重新進行單元測試。

Java代碼  
  1. //Dependency Inversion Principle - Good example     
  2. interface IWorker {     
  3.     public void work();     
  4. }     
  5.     
  6. class Worker implements IWorker{     
  7.     public void work() {     
  8.         // ....working     
  9.     }     
  10. }     
  11.     
  12. class SuperWorker  implements IWorker{     
  13.     public void work() {     
  14.         //.... working much more     
  15.     }     
  16. }     
  17.     
  18. class Manager {     
  19.     IWorker m_worker;     
  20.     
  21.     public void setWorker(IWorker w) {     
  22.         m_worker=w;     
  23.     }     
  24.     public void manage() {     
  25.         m_worker.work();     
  26.     }     
  27. }    

 

4、總結

      應用該原則意味着上層類不直接使用底層類,他們使用接口作爲抽象層。這種情況下上層類中創建底層類的對象的代碼不能直接使用new操作符。可以使用一些創建型設計模式,例如工廠方法,抽象工廠和原型模式。

      模版設計模式是應用依賴倒轉原則的一個例子。

      當然,使用該模式需要額外的努力和更復雜的代碼,不過可以帶來更靈活的設計。不應該隨意使用該原則,如果我們有一個類的功能很有可能在將來不會改變,那麼我們就不需要使用該原則。  

迪米特原則(Law of Demeter)

1、迪米特原則的定義

      迪米特原則也叫最少知識原則(Least Knowledge Principle, LKP)簡單的說,就是如果兩個類不必彼此直接通信,那麼這兩個類就不應當發生直接的相互作用。如果一個類需要調用類另一個類的某個方法的話,應當通過第三個類來轉發調用。迪米特法則可以簡單說成:talk only to your immediate friends。
2、迪米特原則解釋

       一個軟件實體應當儘可能少的與其他實體發生相互作用。每一個軟件單位對其他的單位都只有最少的知識,而且侷限於那些與本單位密切相關的軟件單位。迪米特法則減少耦合的問題,類之間的耦合越弱,越有利於複用,一個處在弱耦合的類被修改,不會對有關係的類造成波及。也就是說,信息的隱藏促進了軟件的複用。

3、迪米特原則含義

     (1)、talk only to your immediate friends.只和直接朋友通信。在一個類中,出現在成員變量、方法的輸入輸出參數中的類被稱爲成員朋友類。

 實例:張三李四是朋友關係,李四王五是朋友關係.張三和王五是陌生人關係,爲了保證張三和王五不認識,而張三又能調用王五中的方法,通過一個李四作爲中繼,把張三和王五隔離開來。

Java代碼  
  1. //抽像的,陌生人;  
  2. public interface AbstractStranger  {  
  3.  public abstract void operation3();  
  4. }  
  5. //某人;(張三)  
  6. public class Someone   {  
  7.  public void operation1(Friend friend){  
  8.   AbstractStranger stranger = friend.provide();    //里氏代換;  
  9.   stranger.operation3();  
  10.  }  
  11. }  
  12. //朋友;(李四)  
  13. public class Friend    {  
  14.  private AbstractStranger stranger = new Stranger();  
  15.  public void operation2(){  
  16.   System.out.println("operation2");  
  17.  }  
  18.  public AbstractStranger provide(){  
  19.   return stranger;  
  20.  }  
  21. }  
  22.   //陌生人(王五);  
  23. public class Stranger implements AbstractStranger{            
  24.  public void operation3(){  
  25.   System.out.println("operation3");  
  26.  }  
  27. }  

     (2)、迪米特法則就要求類“小氣”一點,儘量不要對外公佈太多的public 方法和非靜態的public 變量,儘量內斂,
多使用private,package-private、protected 等訪問權限。

   實例:在軟件的安裝中,一個類中定義了多個步驟,同時一個類要調用此類中的方法完成安裝,此時可以在客戶端的類中隨即地調用這些步驟,這樣前邊的類就定義多個public的方法,我們轉換一種思路,把這些步驟方法都設置爲private,同時,引入一個總的方法,在總的方法中調用安裝步驟方法,避免了客戶類和此類中多個方法的接觸。

Java代碼  
  1. import java.util.Random;  
  2. /** 
  3. * 按照步驟執行的業務邏輯類 
  4. */  
  5. public class Wizard {  
  6. private Random rand = new Random(System.currentTimeMillis());  
  7. //第一步  
  8. private int first(){  
  9. System.out.println("執行第一個方法...");  
  10. return rand.nextInt(100);  
  11. }  
  12. //第二步  
  13. private int second(){  
  14. System.out.println("執行第二個方法...");  
  15. return rand.nextInt(100);  
  16. }  
  17. //第三個方法  
  18. private int third(){  
  19. System.out.println("執行第三個方法...");  
  20. return rand.nextInt(100);  
  21. }  
  22. //軟件安裝過程  
  23. public void installWizard(){  
  24. int first = this.first();  
  25. //根據first返回的結果,看是否需要執行second  
  26. if(first>50){  
  27. int second = this.second();  
  28. if(second>50){  
  29. int third = this.third();  
  30. if(third >50){  
  31. this.first();  
  32. }  
  33. }  
  34. }  
  35. }  
  36. /** 
  37. * 業務組裝類,負責調用各個步驟 
  38. */  
  39. public class InstallSoftware {  
  40. public void installWizard(Wizard wizard){  
  41. //直接調用  
  42. wizard.installWizard();  
  43. }  
  44. }  

 

      (3)、如果一個方法放在本類中,即不增加類間關係,也對本類不產生負面影響,就放置在本類中。組合/聚合與之相似。

     (4)、謹慎使用Serializable。

 4、迪米特原則優缺點

      由於狹義迪米特法則要求當兩個類不必直接通信時,進行方法調用應當由第三個類來轉發調用,這樣就會在系統裏製造出大量的小方法,是的系統顯的凌亂。

      迪米特法則的知識最小化,可以使得局部的模塊得到簡化,但是同時也會造成模塊間的通信效率降低,使模塊間不容易協調。

      迪米特法則討論的是對象之間信息的流量,方向以及信息影響的控制。其主要意圖是控制信息過載,這就要求一個模塊一個應當儘可能將自己的內部數據和實現細節隱藏起來,也就是要求有良好的封裝。

      因此,在類設計上的應用優先考慮將一個類設計成不變類,即使這個類是可變的在給她的屬性設置賦值方法時,也應當儘可能的吝嗇,除非確實需要,否則不要對該屬性增加賦值方法。儘量降低一個類的訪問權限,謹慎使用Serializable,儘量降低成員的訪問權限。

      設計模式的門面模式(Facade)和中介模式(Mediator),都是迪米特法則應用的例子。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章