2012年4月19日

白話 Design Pattern (二) Simple Factory, Factory Method

在GoF的書中將23個Design Patterns分為三類,分別為建構類模式(Creational Patterns)、結構類模式(Structural Patterns)、行為類模式(Behavioral Patterns)。這樣劃分簡單且清楚,就由『 建構類模式(Creational Patterns)』開始探討吧!

建構類模式,顧名思義此類的模式目的在建立出物件實體(instantiate)。

建立物件實體?為什麼還要設計模式?不是用一個 "new" 就好了嗎?

試想以下的實例:
阿甘維護了會員一個系統,原本只有一個類別(class)--Member。今天主管來告訴你,公司現在要多出一種會員身分--黃金會員,他們可以在結帳時享受更優惠的折扣。

阿甘不疾不徐的想了一下,打算這樣做:
1.增加一個新的類別:GoldMember來繼承Member。
2.覆寫結帳時的折扣規則。
3.在建立物件時作個判斷:如果該會員是黃金會員則 new GoldMember( ),否則 new Member( )。

  public class Member
    {
        public decimal GetDiscount()
        {
            ...
        }
    }

    public class GoldMember : Member
    {
        public override decimal GetDiscount()
        {
            ...
        }
    }


    if(會員是金卡會員)
    {
        new GoldMember();
    }
    else
    {
        new Member();
    }

因為計價程式會呼叫折扣函數GetDiscount(),所以完全不用改。

太棒了!看來一切可行!

這時他也注意到了出現 new Member( ) 的程式不只一個地方(可能出現在客服介面、帳單管理介面、會員網站等地方),『那就使用拿手的Copy and Paste大法吧!』,很快的這些地方都被換成以上的程式段。

此時,他發現走到身後的主管正在看著,眼角泛著晶瑩的淚光,『一定是我的工作效率太棒了,使用了OO的優點,一下子就把問題解決了,讓主管不禁留下欣慰的眼淚吧!』阿甘不禁得意起來。

如果你正是主管,發現他的問題了嗎?

Copy不是該死,但會把維護的人搞死!

是的,把相同的程式片段分散在多數地方,絕對是開發的大忌。如果日後公司又要增加一種鑽石會員,那這些片段豈不是要一個個的修改,若有地方疏漏就會造成問題,況且需求變動就要到處修改原先的程式,更是違反了OCP(開放封閉原則)

即便上述的程式沒有重複出現,把應該產生哪種會員的規則放到某處,造成該處多了增加新的會員身分就需變動的理由,也是違反了SRP(單一職則原則)

那就把該建立哪一個會員的程式片段抽出來吧!

此外,如果你還想到DIP(依賴倒轉)原則,而能將GoldMember與Member的繼承關係,改為一起繼承一個抽象型別(abstract class)或介面(interface)...(其實少用繼承也是SOLID外的開發原則,以後有機會再提吧!)

賓果!你已經進入Design Patterns的思考領域了!!


以上就是簡單工廠模式(Simple Factory Pattern)的圖示。

簡單工廠模式又稱靜態工廠模式,簡單的說就是把建立哪一個Product的判斷,放在一個專門的類別裡,而它傳回的型態即是將產品抽象後的abstract class或interface,可以說是一個簡單又好用的模式(雖然它並非GoF提出的23個之中)。

這裡使用了『工廠(Factory)』這個名詞,是把其建構出來的物件當成產品,因而自稱建構者為工廠。

大致程式如下:

    public class SimpleFactory
    {
        public Product CreateProduct(string ProductName)
        {
            if(ProductName == "A")
            {
                return(new ProductA());
            }
            else if (ProductName == "B")
            {
                return (new ProductB());
            }
            else
            {
                return(null);
            }
        }
    }

    public interface Product
    {
    }

    public class ProductA : Product
    {
    }

    public class ProductB : Product
    {
    }

但是,它並沒有百分之百滿足OCP,因為每次多了一種新的產品,就必須修改這個判斷式的部分。這時有一個改良作法是使用反射(reflection),但這個先不在此探討。

另一個改良法則是工廠方法模式(Factory Method Pattern),將工廠的部分也抽象化。這樣一來會產生多個對應產品的工廠,此後擴充產品時亦要擴充相對的工廠,看起來複雜的多。


要徹底了解差異,可以舉一個生活實例:

某學校的學生餐廳有兩個廚師-王師傅與周師傅,王師傅負責炒飯,周師傅負責煮麵。餐廳點餐的模式有以下三種:

(1)沒有Patterns:學生要自己知道自己想吃的東西是哪個師傅作的,然後自己去點。
萬一哪天其中一個師傅換人了,則所有的學生都要知道自己是不是該找新的廚師。

(2)簡單工廠模式:餐廳設置了一個點餐台,後方站一位正妹服務員,櫃台前印著精美的Menu。學生向服務員點餐後,該員再向後台的廚師請求。無論廚師怎麼換,學生找的都是這個點餐台。

(3)工廠方法模式:餐廳設計成料理東西軍模式。左邊是點飯的服務台,上面大大的招牌寫『天下無敵蛋炒飯 』;右邊是點麵的服務台,掛著『世界第一大魯麵』,學生想吃飯就向左走,吃麵的向右走。中間還可以放個人型立牌,寫上『今天你想吃哪一道,Dochi?』



想想,如果今天餐廳要加賣『鍋貼』,則以上三種情況差別在哪?

(1)沒有Patterns:需要點鍋貼的學生都要更新一下資訊,認識新的鍋貼師傅。

(2)簡單工廠模式:點餐台的服務員需要重新訓練,櫃台前的Menu要重新製作,將鍋貼加上去。學生則維持走向點餐台即可。

(3)工廠方法模式:在餐廳中間加設一個鍋貼點餐台,原先兩邊的點餐台完全不用動(符合開放封閉原則)。學生則各自走向正確的點餐台。


又, 『鍋貼』 改由周師傅負責製作,煮麵的則另聘一位林師傅,會有甚麼改變?

(1)沒有Patterns:學生又要更新一次資訊。部分改不過來的學生也許因此崩潰...

(2)簡單工廠模式:點餐台的服務員需要重新訓練,學生無影響。

(3)工廠方法模式:點麵、鍋貼的服務員重新訓練就好(點飯的服務員完全不影響),學生無影響。

由此可知,開發時期若使用了較有彈性的結構,可能讓開發成本提高,但對於之後的設計變更,便能以較小的代價因應。

那麼結構越複雜的模式一定越好?當然不是,如果你知道這個部分將來變動的機會很低,其實也沒必要殺雞卻急著打造一把牛刀。也許等將來遇到需要的時候,再來重構就好。

重點是,如果你不懂這些設計模式,你的評估一定不夠周詳。


2 則留言: