Java Tools

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            83 隨筆 :: 0 文章 :: 16 評論 :: 0 Trackbacks

          法則1:優先使用(對象)組合,而非(類)繼承

          [ Favor Composition Over Inheritance ]




          • 組合



            1.(對象)組合是一種通過創建一個組合了其它對象的對象,從而獲得新功能的復用方法。

            2.將功能委托給所組合的一個對象,從而獲得新功能。

            3.有些時候也稱之為"聚合"(aggregation)或"包容"(containment),盡管有些作者對這些術語賦予了專門的含義

            4.例如:

            a.聚合:一個對象擁有另一個對象或對另一個對象負責(即一個對象包含另一個對象或是另一個對象的一部分),并且聚合對象和其所有者具有相同的生命周期。(譯者注:即所謂的"同生共死"關系,可參見GOF的Design
            Patterns: Elements of Reusable Object-Oriented Software的引言部分。)

            b.包容:一種特殊類型的組合,對于其它對象而言,容器中的被包含對象是不可見的,其它對象僅能通過容器對象來訪問被包含對象。(Coad)





            5.包含可以通過以下兩種方式實現:

            a.根據引用(By reference)

            b.根據值(By value)

            6.C++允許根據值或引用來實現包含。

            7.但是在Java中,一切皆為對象的引用!




          • 組合的優點和缺點






            1.優點:

            a.容器類僅能通過被包含對象的接口來對其進行訪問。

            b."黑盒"復用,因為被包含對象的內部細節對外是不可見。

            c.對裝性好。

            d.實現上的相互依賴性比較小。(譯者注:被包含對象與容器對象之間的依賴關系比較少)

            e.每一個類只專注于一項任務。

            f.通過獲取指向其它的具有相同類型的對象引用,可以在運行期間動態地定義(對象的)組合。




            2.缺點:

            a.從而導致系統中的對象過多。

            b為了能將多個不同的對象作為組合塊(composition block)來使用,必須仔細地對接口進行定義。




          • 繼承





            1.(類)繼承是一種通過擴展一個已有對象的實現,從而獲得新功能的復用方法。

            2.泛化類(超類)可以顯式地捕獲那些公共的屬性和方法。

            3.特殊類(子類)則通過附加屬性和方法來進行實現的擴展。





          • 繼承的優點和缺點





            1.優點:

            a.容易進行新的實現,因為其大多數可繼承而來。

            b.易于修改或擴展那些被復用的實現。

            2.缺點:

            a.破壞了封裝性,因為這會將父類的實現細節暴露給子類。

            b. "白盒"復用,因為父類的內部細節對于子類而言通常是可見的。

            c.當父類的實現更改時,子類也不得不會隨之更改。

            d.從父類繼承來的實現將不能在運行期間進行改變。





          • Coad規則





            僅當下列的所有標準被滿足時,方可使用繼承:

            a.子類表達了"是一個…的特殊類型",而非"是一個由…所扮演的角色"。

            b子類的一個實例永遠不需要轉化(transmute)為其它類的一個對象。

            c.子類是對其父類的職責(responsibility)進行擴展,而非重寫或廢除(nullify)。

            d.子類沒有對那些僅作為一個工具類(utility class)的功能進行擴展。

            e.對于一個位于實際的問題域(Problem Domain)的類而言,其子類特指一種角色(role),交易(transaction)或設備(device)。





          • 繼承/組合示例1







          1."是一個…的特殊類型",而非"是一個由…所扮演的角色"

          -->失敗。乘客是人所扮演的一種角色。代理人亦然。

          2.永遠不需要轉化

          -->失敗。隨著時間的發展,一個Person的子類實例可能會從Passenger轉變成Agent,再到Agent Passenger。

          3.擴展,而非重寫和廢除

          -->通過。

          4.不要擴展一個工具類

          -->通過。

          5.在問題域內,特指一種角色,交易或設備

          -->失敗。Person不是一種角色,交易或設備。






          繼承并非適用于此處!


          使用組合進行挽救!





          • 繼承/組合示例2







          1."是一個…的特殊類型",而非"是一個由…所扮演的角色"

          -->通過。乘客和代理人都是特殊類型的人所扮演的角色。

          2.永遠不需要轉化

          -->通過。一個Passenger對象將保持不變;Agent對象亦然。

          3.擴展,而非重寫和廢除

          -->通過。

          4.不要擴展一個工具類

          -->通過。

          5.在問題域內,特指一種角色,交易或設備

          -->通過。PersonRole是一種類型的角色。




          繼承適用于此處!




          • 繼承/組合示例3







          1."是一個…的特殊類型",而非"是一個由…所扮演的角色"

          -->通過。預訂和購買都是一種特殊類型的交易。

          2.永遠不需要轉化

          -->通過。一個Reservation對象將保持不變;Purchase對象亦然。

          3.擴展,而非重寫和廢除

          -->通過。

          4.不要擴展一個工具類

          -->通過。

          5.在問題域內,特指一種角色,交易或設備

          -->通過。是一種交易。




          繼承適用于此處!




          • 繼承/組合示例4







          1."是一個…的特殊類型",而非"是一個由…所扮演的角色"

          -->失敗。預訂不是一種特殊類型的observable。

          2.永遠不需要轉化

          -->通過。一個Reservation對象將保持不變。

          3.擴展,而非重寫和廢除

          -->通過。

          4.不要擴展一個工具類

          -->失敗。Observable就是一個工具類。

          5.在問題域內,特指一種角色,交易或設備

          -->不適用。Observable是一個工具類,并非一個問題域的類。。




          繼承并非適用于此處!




          • 繼承/組合總結





            1.組合與繼承都是重要的重用方法

            2.在OO開發的早期,繼承被過度地使用

            3.隨著時間的發展,我們發現優先使用組合可以獲得重用性與簡單性更佳的設計

            4.當然可以通過繼承,以擴充(enlarge)可用的組合類集(the set of composable classes)。

            5.因此組合與繼承可以一起工作

            6.但是我們的基本法則是:

            優先使用對象組合,而非(類)繼承

            [ Favor Composition Over Inheritance ]

            法則2:針對接口編程,而非(接口的)實現

            [ Program To An Interface, Not An Implementation ]





            • 接口



              1.接口是一個對象在對其它的對象進行調用時所知道的方法集合。

              2.一個對象可以有多個接口(實際上,接口是對象所有方法的一個子集)

              3.類型是對象的一個特定的接口。

              4.不同的對象可以具有相同的類型,而且一個對象可以具有多個不同的類型。

              5.一個對象僅能通過其接口才會被其它對象所了解。

              6.某種意義上,接口是以一種非常局限的方式,將"是一種…"表達為"一種支持該接口的…"。

              7.接口是實現插件化(pluggability)的關鍵





            • 實現繼承和接口繼承



              1.實現繼承(類繼承):一個對象的實現是根據另一個對象的實現來定義的。

              2.接口繼承(子類型化):描述了一個對象可在什么時候被用來替代另一個對象。

              3.C++的繼承機制既指類繼承,又指接口繼承。

              4.C++通過繼承純虛類來實現接口繼承。

              5.Java對接口繼承具有單獨的語言構造方式-Java接口。

              6.Java接口構造方式更加易于表達和實現那些專注于對象接口的設計。





            • 接口的好處



              1.優點

              a.Client不必知道其使用對象的具體所屬類。

              b.一個對象可以很容易地被(實現了相同接口的)的另一個對象所替換。

              c.對象間的連接不必硬綁定(hardwire)到一個具體類的對象上,因此增加了靈活性。

              e.松散藕合(loosens coupling)。

              f.增加了重用的可能性。

              e.提高了(對象)組合的機率,因為被包含對象可以是任何實現了一個指定接口的類。

              2.缺點:

              a.設計的復雜性略有增加

              (譯者注:接口表示"…像…"(LikeA)的關系,繼承表示"…是…"(IsA)的關系,組合表示"…有…"(HasA)的關系。)





            • 接口實例








            該方法是指其它的一些類可以進行交通工具的駕駛,而不必關心其實際上是(汽車,輪船,潛艇或是其它任何實現了IManeuverabre的對象)。


            法則3:開放-封閉法則(OCP)

            軟件組成實體應該是可擴展的,但是不可修改的。

            [ Software Entities Should Be Open For Extension, Yet Closed For Modification
            ]




            • 開放-封閉法則





              1.開放-封閉法則認為我們應該試圖去設計出永遠也不需要改變的模塊。

              2我們可以添加新代碼來擴展系統的行為。我們不能對已有的代碼進行修改。

              3.符合OCP的模塊需滿足兩個標準:

              4.可擴展,即"對擴展是開放的"(Open For Extension)-模塊的行為可以被擴展,以需要滿足新的需求。

              5.不可更改,即"對更改是封閉的"(Closed for Modification)-模塊的源代碼是不允許進行改動的。

              6.我們能如何去做呢?

              a.抽象(Abstraction)

              b.多態(Polymorphism)

              c.繼承(Inheritance)

              d.接口(Interface)


              7. 一個軟件系統的所有模塊不可能都滿足OCP,但是我們應該努力最小化這些不滿足OCP的模塊數量。

              8.開放-封閉法則是OO設計的真正核心。

              9.符合該法則便意味著最高等級的復用性(reusability)和可維護性(maintainability)。





            • OCP示例



              1. 考慮下面某類的方法:





              2.以上函數的工作是在制訂的部件數組中計算各個部件價格的總和。

              3.若Part是一個基類或接口且使用了多態,則該類可很容易地來適應新類型的部件,而不必對其進行修改。

              4.其將符合OCP


              5. 但是在計算總價格時,若財務部頒布主板和內存應使用額外費用,則將如何去做。

              6.下列的代碼是如何來做的呢?





              7.這符合OCP嗎?

              8.當每次財務部提出新的計價策略,我們都不得不要修改totalPrice()方法!這并非"對更改是封閉的"。顯然,策略的變更便意味著我們不得不要在一些地方修改代碼的,因此我們該如何去做呢?

              9.為了使用我們第一個版本的totalPrice(),我們可以將計價策略合并到Part的getPrice()方法中。


              10.這里是Part和ConcretePart類的示例:



              11. 但是現在每當計價策略發生改變,我們就必須修改Part的每個子類!

              12.一個更好的思路是采用一個PricePolicy類,通過對其進行繼承以提供不同的計價策略:




              13.看起來我們所做的就是將問題推遲到另一個類中。但是使用該解決方案,我們可通過改變Part對象,在運行期間動態地來設定計價的策略。

              14.另一個解決方案是使每個ConcretePart從數據庫或屬性文件中獲取其當前的價格。





            • 單選法則



              單選法則(the Single Choice Principle)是OCP的一個推論。

              無論在什么時候,一個軟件系統必須支持一組備選項,理想情況下,在系統中只能有一個類能夠知道整個的備選項集合。


              法則4:Liskov替換法則(LSP)

              使用指向基類(超類)的引用的函數,必須能夠在不知道具體派生類(子類)對象類型的情況下使用它們。

              [ Function Thar Use Referennces To Base(Super) Classes Must Be Able To Use Objects
              Of Derived(Sub) Classes Without Knowing It ]




              • Liskov替換法則





                1.顯而易見,Liskov替換法則(LSP)是根據我所熟知的"多態"而得出的。

                2.例如:



                方法drawShape應該可與Sharp超類的任何子類一起工作(或者,若Sharp為Java接口,則該方法可與任何實現了Sharp接口的類一起工作)

                但是當我們在實現子類時必須要謹慎對待,以確保我們不會無意中違背了LSP。





                3.若一個函數未能滿足LSP,那么可能是因為它顯式地引用了超類的一些或所有子類。這樣的函數也違背了OCP,因為當我們創建一個新的子類時,會不得不進行代碼的修改。




              • LSP示例



                1. 考慮下面Rectangle類:



                2.現在,Square類會如何呢?顯然,一個正方形是一個四邊形,因此Square類應該從Rectangle類派生而來,對否?讓我們看一看!

                3.觀察可得:

                a.正方形不需要將高和寬都作為屬性,但是總之它將繼承自Rectangle。因此,每一個Square對象會浪費一點內存,但這并不是一個主要問題。

                b.繼承而來的setWidth()和setHeight()方法對于Square而言并非真正地適合,因為一個正方形的高和寬是相同。因此我們將需要重寫setWidth()和setHeight()方法。不得不重寫這些簡單的方法有可能是一種不恰當的繼承使用方式。


                3.Square類如下:










              4. 看起來都還不錯。但是讓我們檢驗一下!








              5. 測試程序輸出:







              6.看上去好像我們違背了LSP!


              7.這里的問題出在哪里呢?編寫testLsp()方法的程序員做了一個合理的假設,即改變Rectangle的寬而保持它的高不變。

              8.在將一個Square對象傳遞給這樣一個方法時產生了問題,顯然是違背了LSP

              9.Square和Rectangle類是相互一致和合法的。盡管程序員對基類作了合理的假設,但其所編寫的方法仍然會導致設計模型的失敗。

              10.不能孤立地去看待解決方案,必須根據設計用戶所做的合理假設來看待它們。


              11. 一個數學意義上的正方形可能是一個四邊形,但是一個Square對象不是一個Rectangle對象,因為一個Square對象的行為與一個Rectangle對象的行為是不一致的!

              12.從行為上來說,一個Square不是一個Rectangle!一個Square對象與一個Rectangle對象之間不具有多態的特征。






              • 總結





                1.Liskov替換法則(LSP)清楚地表明了ISA關系全部都是與行為有關的。

                2.為了保持LSP(并與開放-封閉法則一起),所有子類必須符合使用基類的client所期望的行為。

                3.一個子類型不得具有比基類型(base type)更多的限制,可能這對于基類型來說是合法的,但是可能會因為違背子類型的其中一個額外限制,從而違背了LSP!

                4.LSP保證一個子類總是能夠被用在其基類可以出現的地方!

          posted on 2007-07-02 13:11 和田雨 閱讀(215) 評論(0)  編輯  收藏 所屬分類: 文檔

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 静乐县| 日照市| 小金县| 文安县| 沂南县| 自治县| 林州市| 买车| 万安县| 澄江县| 封开县| 阜城县| 运城市| 百色市| 项城市| 台北县| 临漳县| 万宁市| 凤冈县| 科尔| 七台河市| 柳河县| 本溪市| 珲春市| 贞丰县| 莒南县| 七台河市| 伊通| 商河县| 息烽县| 昌邑市| 聂拉木县| 寿光市| 临沧市| 新巴尔虎右旗| 台南县| 改则县| 清河县| 深州市| 苏尼特右旗| 怀集县|