編程規則 – 3 類設計規則 類設計的基本要求 3.2

來源:互聯網
上載者:User
文章目錄
  • 3.2.1 開閉原則 Open-Closed Principle(OCP)
  • 3.2.2 單一職責原則 Single Responsibility Principle (SRP)
  • 3.2.3 介面分隔原則 Interface Segregation Principle (ISP)
  • 3.2.4 依賴倒置原則 Interface Segregation Principle (ISP)
  • 3.2.5 裡氏替換原則 Interface Segregation Principle (ISP)
  • 3.2.6 合成複用原則 Composite Reuse Principle CRP
  • 3.2.7 最少知識原則Least Knowledge Principle(LKP)

上篇:http://blog.csdn.net/xabcdjon/article/details/6707098    開篇:編程規則 1請參考:方法設計規則   方法是類的核心,要想設計好類,必須遵循方法的設計原則 3.2 類設計的基本原則

   類是屬性和方法(行為)的容器,但它不是垃圾桶,更不能是四像八不像。

    類是對技術領域和業務領域客觀實體(可能是虛擬實體)的抽象和表達,必須反映其真實的本質的屬性和行為,杜絕人為想象和臆造,不良的設計中充斥著形色各異的四像八不像的怪獸,怪獸橫行系統自然難於駕控。為了避免此類狀況的發生,設計出良好的類,請遵循如下基本原則。

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

      軟體實體(模組,類,方法等)應該對擴充開放,對修改關閉。

      開閉原則是軟體系統設計的最基本的原則,各種設計模式都是為了遵守OCP,而針對某種具體應用情境的一種設計方法。因此軟體工程師必須努力理解和深刻體會開閉原則,並在設計實踐中時刻考慮如何遵守該原則;

      開閉原則比較抽象,不利於理解和實踐,這時需要認真學習設計模式,並搞清楚每種模式適用於何種情境,是如何貫徹開閉原則的,它對什麼開了,對什麼閉了,並在實踐過程中靈活運用,這叫做學招,模式是套路和招數,當你頓悟了,OCP已經深入到了你的骨髓,你舉手投足都會遵循OCP,你就無招勝有招了,你就可以忘掉那些模式了,但你卻在自然而然地、恰當地、了無痕迹地,妙用著那些模式,或建立著更適合於情境的新模式;因此OCP是軟體設計的“道”,道之為用也,無言無為。

      關於設計模式我們會另文介紹,但實際上這些模式通常主要運用於較複雜的系統和平台級軟體,通常的行業應用中較少用到,最常用的Factory 方法,我們的平台已經為你提供萬能通用工廠,你知道如何使用就可以了,單例模式我們也有標準的實現範例,當你需要時參照使用即可,因此你可以不急於瞭解和掌握各種設計模式,更實用的是你認真瞭解和掌握本文介紹的原則和方法。

      根據開閉原則,在設計一個軟體系統模組(類,方法)的時候,應該可以在不修改原有的模組(修改關閉)的基礎上,能擴充其功能(擴充開放)。

      OCP保證了系統:

        1)穩定性。開閉原則要求擴充功能不修改原來的代碼,這可以讓軟體系統在變化中保持穩定。

        2)擴充性。開閉原則要求對擴充開放,通過擴充提供新的或改變原有的功能,讓軟體系統具有靈活的可擴充性。

      而穩定性和擴充性是軟體系統最重要的屬性,因此OCP如此重要。

開閉原則的實現方法

      不允許修改卻能夠拓展,聽起來好像矛盾和不可思議,的確是這樣,如果什麼也不變系統怎麼能夠拓展呢,必須要新增或變動些什麼才能滿足系統拓展的要求,該原則的目的是指導你怎樣以最合理和最小的變動來達到拓展的目的,這裡我把最合理放在前面是非常關鍵的,該變的東西是可以變的,不該變的是不可以變的(似乎是廢話,關鍵是你有時分不清什麼改變什麼不該變,關鍵就在這裡),其實比OCP更基礎的一個原則就是:

      變與不變分離、資料與邏輯分離

      這在上面已經提到過,這是一個至關重要的隱性原則,如果一個系統的設計能夠將變與不變的嚴格分離,資料與邏輯嚴格分離,你就已經遵循了OCP,你的系統就一定具有最好的穩定性和擴充性。

      實現OCP的主要方法就是分離變與不變,例如:

        1) 把不變的行為加以抽象成穩定的介面,不修改介面而拓展(變革)實現,就可以擴充系統;

        2)介面的最小功能設計原則。根據這個原則,原有的介面要麼可以應對未來的擴充;不足的部分可以通過定義新的介面來實現;這樣不至於對原有介面不斷調整和修改。但一定要注意不要以此作為不修改介面的理由,如果發現介面設計不合理,應儘可能地早修改。

        3)模組之間的調用通過抽象介面進行,這樣即使實現層發生變化,也無需修改調用方的代碼。

      介面是更加穩定的抽象,具體實現可能隨需而變。

        4)將參數提煉出來,必要時修改參數就可以實現拓展。

開閉原則的相對性

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

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

      只能讓一個類有且僅有一個職責。這也是單一職責原則含義。

      類要短小。這樣你就可以避免製造怪獸了。

      永遠不要讓一個類存在多個改變的理由。

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

      事實上,如果你在設計類時,使之僅描述一個獨立的客觀事務,而不是將相關、不相關的事務攪在一起描述,你基本就可以不違背這樣原則。

職責的劃分

      既然一個類不能有多個職責,那麼怎麼劃分職責呢?

      Robert.C Martin給出了一個著名的定義:所謂一個類的一個職責是指引起該類變化的一個原因。

      If you can think of more than one motive for changing a class, then that class has more than one responsibility.

      如果你能想到一個類存在多個使其改變的原因,那麼這個類就存在多個職責。

       
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個不同的介面,這樣至少可以讓用戶端應用程式使用具有單一職責的介面:

 

     interface Connection {          public void dial(String pno);    //撥號            public void hangup();        //掛斷      }     interface DataChannel {          public void send(char c);      //發送資料            public char recv();        //接收資料      }
3.2.3 介面分隔原則 Interface Segregation Principle (ISP)

      不能強迫使用者去依賴那些他們不使用的介面。換句話說,使用多個專門的介面比使用單一的總介面總要好,即介面功能要專一化。

它包含了2層意思:

      - 介面的設計原則:介面的設計應該遵循最小介面原則,不要把使用者不使用的方法塞進同一個介面裡。

      如果一個介面的方法沒有被使用到,則說明該介面過胖,應該將其分割成幾個功能專一的介面。

      - 介面的依賴(繼承)原則:如果一個介面a依賴(繼承)另一個介面b,則介面a相當於繼承了介面b的方法,那麼繼承了介面b後的介面a也應該遵循ISP,不應該包含使用者不使用的方法。

      反之,則說明介面a被b給汙染了(b介面中的某些方法a用不到,這意味著a不應該繼承b),應該重新設計它們的關係。

      下面我們舉例說明怎麼設計介面或類之間的關係,使其不違反ISP原則。

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

      有以下幾種設計方法:

      違反ISP原則的例子:

      在Door介面裡定義所有的方法。圖:

 

 

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

      遵循ISP原則的例:

      通過多重繼承實現,有兩種方案:

 

      在Alarm介面定義alarm方法,在Door介面定義lock,unlock方法。介面之間無繼承關係。CommonDoor實現Door介面,AlarmDoor有2種實現方案:

     1)同時實現Door和Alarm介面。

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

     第2)種方案更具有實用性。

     這兩種設計都遵循了ISP設計原則。

     ISP為我們對介面抽象的顆粒度上建立了判斷基準:在為系統設計介面的時候,使用多個專門的介面代替單一的胖介面是更好的設計思路。

3.2.4 依賴倒置原則 Interface Segregation Principle (ISP)

      依賴倒置原則的核心意思是說,要依賴抽象,而不要依賴具體,它包括:

     A. 高層模組不應該依賴於低層模組,二者都應該依賴於抽象

     B. 抽象不應該依賴於細節,細節應該依賴於抽象

     因為抽象(抽象類別或介面)是穩定的本質的,具體實作類別則是細節的,易變的。

     依賴:在程式設計中,如果一個類a使用了另一個類b,我們稱a依賴b。

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

      因為傳統的結構化程式設計中,高層模組總是依賴於低層模組;這樣底層模組的變動嚴重影響高層模組,違反開閉原則,物件導向程式設計中,我們遵守DIP可以將這種關係倒轉過來,這就是DIP名稱的由來。

      “高層”依賴“低層”是一種不良設計,它會導致牽一髮動全身,使系統僵硬、脆弱、不易拓展。

      一個良好的設計應該是系統的每一部分都是可替換的、易替換的。

      但是系統中這種依賴關係又是必然存在的,例如我們前面強調要將基礎業務層與功能分離,一分離就需要耦合,而按普通java的依賴方法,我們勢必要在功能層的類中出現如下的類似代碼:

      xxxBasSrv xxx = new xxxBasSrv(); //執行個體化基礎服務類

      xxx.doXxxSrv();  //執行具體服務處理;

      這就違反了DIP原則(高層依賴了底層),你馬上會說如果這樣就算違背DIP,那我們可能總在違背。是的,關於這一點我們後面會給出回覆。我們先闡述一下概念,類有幾種耦合關係,有三種:

      A)  零耦合;兩個類毫無關聯,稱為零耦合。

      B)  具體耦合;一個具體的類直接在屬性或方法中使用另一個類。

      C)  抽象耦合;一個類通過介面或抽象類別與另一個類別結合程度。

     DIP原則實際就是要求我們在設計時使用抽象耦合,減少具體耦合。

     DIP是實現OCP的重要機制和手段,但是對設計也提出了更高的要求,必須設計和提煉出相對穩定的介面或抽象類別,同時通過Factory 方法,避免類似

    xxxBasSrv xxx = new xxxBasSrv(); //執行個體化基礎服務類的具體依賴語句;因此你會發現遵守DIP需要付出代價(當然你也會得到豐厚的回報),當然如果你機械地遵守,可能反受其累,所以必須掌握好分寸,為此給出如下指導性原則:

      1)    在關鍵的層與層之間必須嚴格遵守DIP,並通過相應的工廠擷取具體類對象。

      2)    對於底層通用方法、工具類等發生變化的可能性很小(尤其是方法參數很穩定)的,通過抽象耦合能夠帶來的好處極其有限,這時使用具體耦合反而更好。

      我們外設管理體系就很好地貫徹了DIP原則,首先我們定義了幾類外設的操作介面,為使用者提供一個裝置服務工廠,具體類在使用外設時通過該工廠就可以拿到具體的外設操作類,通過介面就可以使用外設了,工廠能夠根據具體的外設描述參數,決定執行個體化哪個具體類,這樣隔離了變化,同時為變更和修改帶來了靈活性。

3.2.5 裡氏替換原則 Interface Segregation Principle (ISP)

      裡氏替換原則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體現了:

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

      - 動作正確性保證:符合LSP設計原則的類的擴充不會給已有的系統引入新的錯誤。

      一個規範的類中任何方法,都應有一個前提條件以及一個後續條件,前提條件說明方法接受什麼樣的參數資料等,只有前提條件得到滿足時,這個方法才能被執行;同時後續條件用來說明這個方法完成時的狀態,如果一個方法的執行會導致這個方法的後續條件不成立,那麼這個方法也不應該正常返回。

      LSP要求子類中如果覆蓋父類的方法,應該滿足:

        1)前提條件不強於父類.

         2)後續條件不弱於父類.

      在很多情況下,在設計初期我們類之間的關係不是很明確,LSP則給了我們一個判斷和設計類之間關係的基準:需不需要繼承,以及怎樣設計繼承關係。這很重要,繼承是OO的最重要特徵,但亂繼承則是製造混亂的元兇,我們看客觀世界,繼承僅發生在直系親緣的個體之間,而我們在進行軟體設計時往往會亂點鴛鴦譜,胡亂繼承,製造混亂,LSP指導我們做好繼承關係的設計。

3.2.6 合成複用原則 Composite Reuse Principle CRP

      合成複用原則就是在一個新的類中使用一些已有的類,使之成為新類的一部分;新的類通過向這些類的委派達到複用已有功能的目的。這我們經常在使用,似乎並不是一個什麼原則,但我們應該注意它強調的是:要恰當地使用合成/彙總,而不要輕易使用繼承。

      雖然繼承與合成/彙總都是複用已有類的重要方式,但繼承必須慎用,父類與子類之間必須滿足is-a的關係,即子類是父類的一種;而has-a的關係要採用合成/彙總方式。濫用繼承的例子很多,我們在設計過程也會不經意的犯類似錯誤,包括在Java的API中也存在違反CRP的例子:java.util.Properties

class Properties extends Hashtable<Object,Object> {   ……}

      Properties 是用來存放索引值對的對象,它並不是Hashtable的一種,只是想通過Hashtable存放kev-val,其實也可以使用其他容器存放key-val,這是一個典型的has-a,使用繼承是非常不妥當的,是亂認親屬,會帶來許多不良後果。

      類似的情況我們必須高度重視,換一種話說在設計過程一定要慎用繼承,但並不是不用繼承,恰恰相反,我們要求在應用體系設計時,某一品種的類必須繼承該種的父類(或介面),不允許出現特立獨行,天下至尊的類,關於這一定後面還會提到。

3.2.7 最少知識原則Least Knowledge Principle(LKP)

      又叫迪米特法則(Law of Demeter),該原則的意思是說:一個類應該儘可能少地瞭解其他類。在類設計這個領域,朋友圈越小越好(只要保證履行你的職責),不提倡四海之內皆兄弟;最好是小國寡民、使民(類)無知。它有多重表達方式:

      只與你直接的朋友通訊;

      不要跟陌生人說話;

      每個軟體單元與其他單元都只有最少的知識,而且僅通過最少的介面互動。

      該原則就是在貫徹軟體設計的基本教義—資訊隱藏,資訊隱藏可以使子系統(或類)之間脫耦,從而允許它們獨立地開發、最佳化、使用、閱讀和修改,降低相互之間的影響。

      一個系統越大資訊隱藏就越重要,LKP原則主要用途就是努力實現軟體單元之間的解耦,通過資訊隱藏控制資訊過載,提高系統的靈活性和拓展能力。運用LKP原則在設計要注意一下幾點:

      宏觀層面

       A)系統與系統之間必須通過公布的API介面調用,不允許直接使用另一個系統的具體類,更不允許直接存取對方資料庫,萬不得已可以使用另一系統提供的資料庫檢視。

       B)一個系統內部不同層之間必須通過API介面或服務工廠實現調用。

      微觀層面

A)  降低類的存取權限,例如不需要外界使用的類,就不要使用public,而使用預設的package-private,這樣這些類修改時的影響範圍就僅局限於內部了。

B)  儘可能降低類中成員的訪問權,能私人的就不定義為包級,能定義為包級的就不要定義為public,類中的屬性儘可能不提供修改方法(資料類除外)。

C)  儘可能降低一個類對其他類的依賴。

D)  限制局部變數的有效範圍,在哪使用在哪定義。

 

      總結:以上各原則都是在貫徹OCP原則,設計的關鍵在於抽象,抽象是在對具體事務進行認真分析、歸納、總結基礎上的升華,它撥雲見日,令你看到事務的本質,抽象是設計的基礎,抽象的能力需要你在設計實踐過程中不斷提高。依賴抽象的設計才是設計,依賴具體的開發其實就是功能導向,簡單模仿,一定是糟糕的設計,做好設計必須遵循上述原則,努力做好抽象、資訊隱藏、分離變與不變、分離資料和邏輯。

抽象你可能還是不理解,下面舉例說明:

1)通過對業務領域的分析,提煉出系統使用的全部資料類型、資料字典,名詞及命名字典。

2)通過對業務領域的分析,提煉出該領域的全部實體物件,併合理地分類,建立結構清晰合理的實體類圖結構,同時完成資料庫設計。

3)通過對業務領域全部功能的分析,提煉出支撐各功能的傳輸對象,併合理地分類,建立結構清晰合理的行為類圖結構。

4)通過對業務領域全部功能的分析,提煉出支撐各功能的後台服務介面,併合理地分類,建立結構清晰合理的服務介面類圖結構。

5)通過對業務領域全部功能的細節的深層分析,提煉出支撐基礎處理服務類,確立相應介面,併合理地分類,建立結構清晰合理的基礎服務類圖結構。

      事實上你能夠做到這些,就已經很好地完成了系統的架構設計,具體方法邏輯的設計可在詳細設計過程中完成,這些就是抽象的具體成果。

 

     為了使你的編碼很快提升一個層次,建議你從頭學習:編程規則,你如果能夠很好地理解並認真實踐,你會有質的飛躍。

 

下篇:http://blog.csdn.net/xabcdjon/article/details/6707955

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.