2012年5月11日

白話 Design Pattern (七) Adapter Pattern, Abstract Server Pattern

想像一下,如果今天你的主管交代了一個任務,要你從一台老舊的電腦找一份遠古時期的資料。你試了一下,很幸運的它還能開機,但是,它竟然沒有USB介面來連接你的滑鼠,而這個年代,已經沒有人生產PS/2滑鼠了,要怎麼辦?難道要到拍賣場找一隻堪用的嗎?

如果你知道有『USB to PS/2 轉接頭』這樣的東西,相信這個問題根本不是問題。

事實上,生活中我們的確使用了許許多多的轉接頭,如:電源線三孔轉兩孔、手機用micro usb 轉 usb線、出國用萬用插座...,因為有了轉接器,讓我們可以在不同規格間使用某些設備。

Adapter Pattern正是轉接器。主要目的便是藉由轉接方式,解決介面不同的問題。它的結構也很簡單,就是一頭接我們要的介面,另一頭接提供功能的元件,然後作對應。




使用時機:通常都不是自己新開發的系統,而是為了讓舊有的元件得以繼續運作,或是為了某些外購的元件。


==================================

在Agile Software Development, Principles, Patterns, and Practices一書中,另外提到一個可搭配Adapter的Abstract Server Pattern,它一樣很簡單,卻也是常出現的基本概念。

Abstract Server Pattern就是在原本直接的關聯中加入一個抽象,好讓物件間的藕合度降低。簡單的說,就是DIP(依賴倒轉)原則。

書中的例子(圖上部),原本的設計將『燈』與『開關』直接關聯,這樣一來有開關就必須有燈不可。加入一個interface後,就能將其間的關聯性減輕,日後如果電扇要用這個開關,也只要繼承抽象的interface即可。



如果考慮到使用開關的物件可能來自外部元件或舊有的元件,而無法遵循現在開發的interface,那搭配了Adapter就能進一步達成(如下圖)。



本文將這些模式說得很簡單,其實也是想說其實設計模式未必是艱澀難懂,或需要強記的,事實上,即使是沒聽過設計模式的人,如果在撰寫程式時,考慮了減輕耦合、增加彈性之類的概念,也有可能設計出接近這樣的結構。

學習設計模式不是為了找地方套用這些架構,而是你遇到難以取捨之處,可以參考看看你的設計是否考慮周詳,故了解每個模式的精神、形成原因才是最重要的。



2012年5月9日

白話 Design Pattern (四) Builder pattern


相較於工廠系列的模式,著重在該產生哪一個物件;我們也可能面臨到建構過程本身就複雜的狀況,這時如何組裝出一個物件就可能是要解決的問題了,這裡要提到的是建構者模式(Builder pattern)

以學生餐廳的例子,如果將聚焦在點餐櫃台的動作,我們發現服務人員在接獲點餐後,分別有以下幾個動作:

炒飯櫃台:1.放上餐盤 、2. 放上免洗筷與鐵湯匙、3.放上炒飯、4.放上附湯。

麵點櫃台:1.放上餐盤 、2. 放上筷子與長湯匙、3.放上麵品、4.放上飲料 。


想想看,怎麼作比較好?

1.這次的重點在餐點的產生過程比較複雜,最好能夠將過程侷限在服務人員身上,點餐的學生只要享用該完成品,不需知道,也不需參予整個過程。

2.仔細分析,櫃台人員都有相同的四個動作,抽象的描述為:
1) 放上餐盤、2) 放上餐具、3) 放上餐點、4) 放上附湯飲料。

3.所以我們可以想像,整個模型有兩個部分:

1)執行四個動作:這部分兩個櫃台都一樣,見下圖的點餐服務

2) 對四個動作的各自表述:有一個抽象的餐點Builder與繼承它的炒飯Builder、麵食Builder


故,Builder Pattern的類別圖如下:

其中的Director就是負責執行各步驟並產出產品,Builder則確保了其繼承者都會有N個步驟。也有人將Director合併到Builder內,成為一種較簡化的變形。而這個模式中的幾個建構步驟,也可能與工廠系列的模式合作。

總之,Builder模式的重點有:

1.生產的流程複雜,需要被封裝。

2.每個builder都有相同的N個步驟或零件(步驟有先後順序,零件則無)。


前述的用法,產品較類似套餐,即各步驟或零件是固定的,選了A套餐,便能決定了每個部分是什麼。但實務上,如果到速食店點餐,雖然有分:主餐、副食、飲料三個部分,但可能個部分都是消費者當下選擇的(主餐可選雞塊/漢堡/雞堡、副食可選薯條/薯餅...),那就不可能在設計時期(Design Time)把所有的組合Builder都寫好,而需要代入多個參數,像下列:

餐點 A = new 餐點Builder(主食.雞塊, 副食.薯條, 飲料.coca).build();

那麼,如果建構時所需的參數非常多,要怎麼辦?下例是使用了Fluent Interface的技巧,讓新增客戶資料時,可以像以下這樣,簡潔易讀。


客戶 A = new 客戶Builder()
            .Name("Rock")
            .email("xxx@ooo.com")
            .cellphone("09xx123456")
            .build();

大致的寫法是:


public class 客戶
{
   public 客戶(string ID, string Name, string email, string cellphone)
   {
    ...
   }
}

public class 客戶Builder
{
   private 客戶 _Customer;

   string _Name;
   string _email;
   string _cellphone;

   public string Name(string Name)
   {
      _Name = Name;
      return(this);
   }

   public string email(string email)
   {
      _email = email;
      return(this);
   }

   public string cellphone (string cellphone)
   {
      _cellphone = cellphone;
      return(this);
   }

   public 客戶 build()
   {
      _Customer = new 客戶(_Name, _email, _cellphone);
      return(_Customer);
   }

}


最後,如果有人因為看到Builder中包含了多個建置方法,而覺得會跟Abstract Factory搞混,這裡整理了Builder Pattern與Abstract Factory Pattern的不同:

1) Builder著重在隱藏複雜的建置步驟,最後只傳回一個產品。

2) Abstract Factory則是為了維護一系列產品的關聯,會產出某系列的多項產品。

3) Builder模式中,Client不需要認識各個零件的型態。(只要『吃』產出的餐點)

4) Abstract Factory中,Client認識各項的抽象型別或介面,並能使用它們。