1. 程式人生 > >第六章程式碼的可維護性——可維護性的設計模式

第六章程式碼的可維護性——可維護性的設計模式

可維護的設計模式主要分為三種,分別為:創造性模式(前三個)、結構化模式(中間三個)和行為化模式(後五個)。接下來我們將圍繞這三種模式分別進行詳細講解:

1.創造性模式(Creational pattern):

1. 工廠方法模式(Factory Method pattern)

也叫虛擬構造器(Virtual Constructor)。當客戶端不知道要建立哪個具體類的例項,或者不想在客戶端程式碼中指明要具體建立的例項時,可以採用工廠方法。

其實現方法是:定義一個用於建立物件的介面,讓其子類決定例項化哪一個類,從而使一個類的例項化延遲到其子類。

類圖說明:


啊!當然還是要有大栗子的……





其實就是繞了一個彎嘛……不過可不要小瞧這個彎。就是因為這個彎消除了將特定於應用程式的類繫結到程式碼的需要,與此同時程式碼中僅需處理產品介面,因此他可以與任何使用者的產品一起使用。

還記得上一節中的OCP原則麼?這裡就體現了OCP的原則,對拓展開放,對修改已有程式碼的封閉。

2. 抽象工廠模式(abstract Factory)

試想我們現在有一個倉庫類,要控制多個裝置,但是這些裝置的製造商不同,控制介面有差異。那麼顯然對於這種情況工廠模式是不適用的,所以這就需要抽象工廠模式。

其提供了一個介面用來建立一組相關的/相互依賴的物件,但並不需要指明具體類。

還是焗栗子吧:

類圖:


應用:



抽象工廠建立的並不是一個完整的產品,而是一個產品族。什麼是產品族呢?舉個栗子,假設我們要造汽車,我們現在有輪胎,發動機等等一系列造汽車的產品,那麼我們需要將他們組裝起來構成一個產品族這就是我們造好的汽車。注意在抽象工廠中,多個不同種類的產品均有不同種類的型別,比如說發動機分為很多種類,根據使用者需要來採用不同的發動機。也就是說各個產品的建立過程對客戶端可見,但搭配不能改變。本質上 抽象工廠就是把多類產品的工廠方法組合在了一起。

另外嘮叨一句,這裡注意並不是完全的包裝,對於客戶端抽象工廠建立的每一個物件客戶端均知曉!為什麼重點強調這一點?因為下一個模式我們將會提到builder模式,在該模式下才是完全的封裝好的,所有構建細節都會對客戶端隱藏。具體可以往下看。


這是不用抽象工廠的實現,乍一看沒什麼問題,但是假如產品AB分別有兩種型別我們姑且叫他A1/A2和B1/B2,如果規定A1和B2不能組合在一起,那麼這樣顯然就會出現問題。也就是說抽象工廠固定了各個產品的搭配,不能隨意組合。

抽象工廠與普通工廠對比:

  1. 抽象工廠會建立多個型別物件,而普通工廠只會建立一個
  2. 抽象工廠包含有多個工廠方法,而普通工廠只會包含一個
  3. 抽象工廠使用的是組合/委派實現,而普通工廠使用的是繼承/子型別實現

3.構造器模式(Builder)

構建器模式是用來建立一個包含多個部分的複雜物件的。

注意此處客戶端要的不是一堆零散的物件(抽象工廠那樣的結果),客戶端需要的是一個完整的產品,並不關心細節的組成部分。用抽象工廠中的栗子,抽象工廠模式可以理解為汽車組裝公司,他需要知道汽車的各個組成部分並裝起來。而構造器模式可以理解為購買汽車的消費者,其不在乎具體的內部構造如何構建,她僅僅是想開著車出去兜兜風而已。

類圖解釋:


來個大栗子吧!




抽象工廠模式與構建器模式對比:

  • Abstract Factory建立的不是一個完整產品,而是“產品族”(遵循固定搭配規則的多類產品例項),得到的結果是:多個不同產品的例項object,各產品建立過程對client可見,但“搭配”不能改變。
  • Builder建立的是一個完整的產品,有多個部分組成,client不需瞭解每個部分是怎麼建立、各個部分怎麼組合,最終得到一個產品的完整 object 。

模板模式(Template)與構建器模式對比:

  • 模板模式是為了複用演算法中的公共結構(次序)
    定義了一個操作中演算法的骨架(steps),而將具體步驟的實現延遲到子類中,從而複用演算法的結構並可重新定義演算法的某些特定步驟的實現邏輯。其強調步驟的次序,子類override演算法步驟。
  • 構建器模式目的是建立一個複雜的物件,靈活拓展
    將一個複雜物件的構造方法與物件內部的具體表示分離出來,同樣的構造方法可以建立不同的表現。不強調複雜物件內部各部分的“次序”。子類override複雜物件內部各部分的“建立”
    其通過派生出新的builder來構造新的物件體現了OCP原則

2.結構化模式(Structural Patterns)

1.橋接模式(bridge)

橋接模式是OOP最基本的結構模式,通過委派+繼承來建立兩個具體類之間的關係(DIP依賴轉置原則,具體依賴於抽象)

(其實個人理解就是不能更換策略的策略模式)

類圖解釋:


廢話不多說!大荔枝!




(這就是強化的策略模式嘛……)

然而還是說一下對比吧,其實也就是強調方面不同而已:

  • 橋接模式中,強調雙方的run-time委派連線
    一個類A的物件中有其他類B的物件作為其組成部分,但A的物件具體繫結到那個具體子類來實現?在執行時通過委派加以組合並永久儲存這種關係
  • 策略模式中,強到一方的run-time使用另一方的演算法
    “演算法”通常實現為“類的某個方法”的形式,strategy的目的並非在“呼叫演算法的類”與“被呼叫演算法的類”之間建立永久聯絡,只是幫助前者臨時使用後者中的演算法而已,前者無需永久儲存後者例項

2. 代理模式(Proxy)

代理,為什麼要代理呢?當然是被代理的物件可能比較敏感,私密或者貴重等等,不希望被客戶端直接訪問到,所以才設定代理,在二者之間建立防火牆(oh!wall!holy shit!)

類圖解釋:


這個例子比較簡單:




來解釋一下,這並不是說display方法會造成嚴重的代價,其是說明從磁碟裝填檔案需要很嚴重的代價,而display方法需要磁碟中的資料而已,但大部分的程式是不需要這些使代價變的很高的資料的,所以我們就可以使用代理類,如果需要磁碟中的檔案時在執行檔案裝載。這樣就不需要用到一張圖片就裝載一張圖片,即使這個圖片根本就沒有用到display方法。

與介面卡模式的對比(Adaptor):

  • 介面卡模式目的在於消除不相容,其根本目的在於希望B按照客戶端的期望的統一方式與A建立起聯絡。
  • 代理模式目的在於隔離對複雜物件的訪問,降低難度/代價,定位在“訪問/使用行為”

3. 組合模式(Composite)

組合多個物件形成樹形結構以表示具有“整體—部分”關係的層次結構。組合模式對單個物件(即葉子物件)和組合物件(即容器物件)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種物件結構型模式。

類圖:


emm……這個模式,還是看栗子吧……



如何?有什麼感想?這……確定不是資料結構的多叉樹?沒錯啊!這個模式就是強調了一個樹狀的層次結構。

和裝飾器模式(Decorator)對比:

  • 組合模式目的在於在同類物件之間建立起一個樹形層次結構,一個上層物件可以包含多個下層物件
  • 裝飾器模式目的在於強調同類型之間的“特性增加”問題,他們之間是平等的,區別在於擁有特性的多少,每次裝飾只能作用於一個物件。

3.行為化模式(Behavioral Patterns)

1.觀察者模式(Observer)

假設,我們有一個愛抖露idol,她有很多很多的fans,那麼這些小粉絲十分喜歡他們的偶像,想隨時得知偶像的一舉一動,那麼怎麼辦呢?偶像有微博呀,他們就可以去申請偶像的好友位,偶像一旦有故事發生,就會推送給好友位的粉絲(callback)

類圖解釋:



其實也是一個定義看起來很難得模式,實際的程式碼看一個栗子也就懂個基本了:

如果以開頭的偶像粉絲為例,這個是偶像:


粉絲s:

客戶端:


值的一提的是,java中這種模式結構已經幫我們定義好了。需要用的時候直接用就可以啦(不得不說java確實比c++什麼的貼心不少)……


2.訪問者模式(Visitor)

對特定型別的物件的特定操作(visit),在執行時將二者動態的繫結在一起,該操作靈活更改,無需更改被visit的類。

本質上是將資料與作用在資料上的某種特定的操作分離開

類圖解釋:


emm……和策略模式很像,非常像,及其相像!來是來看個程式碼吧:




這……不就是策略模式嘛?沒錯,從實現的角度來看的的確確就是策略模式,委派其他類實現不同的演算法。不過二者又有著根本的不同:

  • 觀察者模式強調的是外部定義某種對ADT的操作,該操作與ADT自身的關係不大(只是訪問ADT),故ADT內部中需要開放accept(visitor)即可,客戶端通過它設定visitor操作並在外部使用。
  • 策略模式強調是對ADT內部某些實現的功能的相應演算法進行靈活的替換。這些演算法是ADT功能的重要組成部分,只不過是委派到外部策略類而已。

也就是說觀察者模式是站在外部客戶端的角度,靈活的增加對ADT的各種不同的操作(哪怕ADT沒實現該操作),策略模式則是站在內部ADT的角度,靈活變化對其內部功能的不同配置。可以這麼理解,策略模式是程式碼複用階段的,是構建ADT時使用的,是應滿足表示獨立性的,如何實現決不能被使用者知道。但是觀察者模式不同,他就是讓使用者選擇不同方法觀察的。他是站在維護的角度上的,這麼說可能容易懂些?

觀察者模式也與迭代器模式有一些相像,現在來說說這兩者區別:

  • 迭代器模式是以遍歷的方式訪問集合資料而無需暴露其內部表示,將“遍歷”這項功能委派到外部iteractor物件。
  • 觀察者模式是在特定ADT上執行某種特定操作,但操作不在ADT內部實現,而是委派到獨立的visitor物件,客戶頓可以靈活地拓展/改變visitor的操作演算法,而不影響ADT。

總而言之就是觀察者模式是站在使用者層面上,可以拓展改變演算法的一種可維護性的設計模式,有點站在迭代模式和策略模式中間的位置的意味。而迭代器模式和策略模式是站在ADT角度的,是可複用性的設計模式。

3.中介/傳遞者模式(Mediator)

多個物件之間要進行互動,不是直接互動,而是通過mediator來實現訊息廣播,從而將物件之間鬆散耦合,利於變化。

類圖解釋:


還是來看個程式碼吧!(這節設計模式怎麼這麼多呀==!)



這是客戶端的使用:


在行為上可能與前面說的觀察者模式類似,所以來談一下對比吧:

  • 觀察者模式是一個物件A對應一組物件的狀態(1對多),A維持這一個對自己感興趣的物件列表,一旦自己發生變化就通知這些物件。雙方地位是不對等的,一個通知,另外一群負責接受。(自己廣播,其他物件接受)
  • 傳遞者模式是一組同類型的物件,彼此間接受發出訊息(多對多),所有物件都是平等的,每個物件都維持這一個傳遞者,將通訊的人物委派給他,自己只負責接受與傳送就好,無需瞭解其他物件,實現瞭解耦。(第三方中介負責廣播)

4.指令模式(Command)

其想解決的問題是客戶端需要執行指令,但不想知道指令的細節,也不想知道指令的具體作用物件。

核心思想:將“指令”封裝為物件,指令的所有細節對客戶端隱藏,在指令內對具體ADT發出動作(呼叫ADT的細節操作)

還是栗子?梨子?李子?例子!




這個倒是個新東西,然而表示沒怎麼用過呀==!

外觀模式和他很像,均強調了對某個複雜系統內部提供的功能的“封裝”,對外提供簡單的呼叫介面,簡化客戶端的使用,隱藏細節,那麼說一下他與外觀模式的區別吧:

  • 指令模式強調了將指令封裝為物件,提供了統一的對外介面。
  • 外觀模式沒有顯示的物件,仍然通過類的方法加以呼叫

5.職責鏈模式(Chain of responsibility)

終於到最後一個了T_T……

當一個請求可能有多種處理模組,各種不確定的情況存在是,不能以“硬編碼”的方式指明按何種次序呼叫處理模組。

所以希望這種模式來避免在請求方與各個處理器之間的緊耦合。

核心思想是:構造流水線,請求在上方傳遞,知道被處理為止。

客戶端只需在流水線入口處發出請求即可,請求自動在流水線上傳遞,直到被處理:

類圖解釋:


由於處理器物件的數目與型別實現都不確定,所以需要動態的配置,使用遞迴組合的方式將多個處理器連成“職責鏈”。請求發起者無需維護所有可能的處理器物件,只需記錄職責鏈的入口:每個處理器只需維護下一個處理器即可,從而簡化了各個處理器之間的連線關係:(有點類似連結串列)


好了!來最後一個大栗子,真的大,大家消化好(終於結束了,客廳滑跪……):






程式碼還是比較簡單的:

相信大家也看出來了,它和訪問者模式都是將“資料”與“作用於資料上的客戶端定製操作”分離開來。

原因:操作可以靈活增加、執行時使用的操作可以動態配置、多個操作執行次序可以動態變化。

但也是存在一定的區別的:

  • 訪問者模式中只定義了一個操作,而職責鏈中定義了一組操作並且規定了他們之間的次序。
  • 訪問者模式中,客戶端建立了訪問者會傳入ADT,ADT再將執行權委派到訪問者中,職責鏈模式中,控制權在各個處理器之間流程,ADT(request物件)完全感受不到。

4.總結:

太多了,來張圖吧:


Ahhhhhhh………………終於說完了,其實這些模式也僅僅是一部分而已,設計模式還有很多,大家感興趣可以去了解一下。對自己的程式碼水平提高很有幫助,然而最重要的還是自己多碼不是麼?當碼過得程式碼多了,再來看設計模式會有一種:誒?這不是我做XXX的時候用到的方法嗎?原來他還有個專門的設計模式呀,難不成我很nb?23333,當然是開玩笑的,不過有了以前的一點經驗再看真的收穫會很多!好了設計模式就說到這裡,下一章我們來說一說異常。嗚啦啦啦啦……