以學生餐廳的例子,原本的學生的需求很簡單,對餐點只有一個『吃』,因此只有點炒飯或魯麵的問題。
但是今天有家長來反應:『吃麵不是有湯喝嗎?那炒飯呢?我的寶貝兒子吃了你們的炒飯,結果差點渴死,你們怎麼負責?想上電視嗎?』於是校長立刻指示,今後餐點除了主食,也要有湯品。(本例純屬虛構)
重點是本來學生只要『吃』,現在增加『喝』的行為。先看下圖為原先工廠方法。
現在會進化成下圖。
意即每個工廠(點餐台)將產生一系列的物件(炒飯與附湯、湯麵與湯麵的湯﹚,以滿足Client的多個需求。日後如果食物增加了鍋貼,自然也要有對應的湯品。
上圖即為抽象工廠模式(Abstract Factory Pattern),其重點是它可以產生一系列的物件,維護同一系列物件之間的關係,而不會讓炒飯配到湯麵的湯。當然它可以更複雜,比如還要增加飯後『甜點』,這樣每個點餐台便會有三個方法(Method),但道理都是一樣的。
而上例是因為客戶端(學生)多了一種行為,導致原先的工廠方法需要擴充。實務上常常是從另一個角度發展出來,例如你原先設計了一套系統可以處理會員、產品、交易資料,結果公司業務擴充後,多出了一個新的產品線,可是兩個產品線的會員處理規則、產品呈現方式、交易行為皆不同。這樣可能原先根本不需要工廠方法的,就要開始拉出抽象的會員介面、產品介面、交易介面,然後去作抽象化的處理。
也許這時候你會寧願把整套系統Copy一份再修改,覺得比較容易些。但是別忘了,下次可能又有新的產品線等著你。而每複製一份系統,將來的維護成本可是要倍增的(萬一發現了一個bug要更新到每一套已經客製的產品裡,除非你的工作是計件算酬勞的,要不然一定會後悔)。
以上的工廠方法、抽象工廠模式,都在實踐一個重要的觀念--把客戶端與實際產生的物件(產品)隔離,便於日後的擴充不互相影響。如一開始的例子:從會員擴充出黃金會員後,將來若還會有鑽石會員,也能將現有修改降至最小。
但,它們在實際使用上還是有一個缺點:客戶端必須在設計時期(Design Time)指定自己要哪一個工廠。就像走進學生餐廳的學生,每個人都天生就要決定自己該吃麵還是飯,而且不能改。
那遇到了會員資料的例子,我們由資料庫讀出一筆會員資料才知道他是哪一種會員,要怎麼辦?只能使用簡單工廠模式?
是否生成A工廠或B工廠,可以更動態的決定?
這就要講到上篇文章提到的反射(reflection)技術,它能藉由傳入組件名稱、物件名稱在執行時期(Run Time)來產生物件,如果搭配簡單工廠,便能不需要原本的判斷式,直接把物件名稱當變數傳入。也有人稱這樣的做法為反射工廠。
程式大致如下:
public class ReflectionFactory
{
public Product CreateProduct(string ProductName)
{
return(Assembly.Load("組件名稱").CreateInstance(ProductName));
}
}
如此,當你遇到普通會員,就傳入"Member"字串,產生出Member物件;如果是黃金會員就傳入"GoldMember",產生 GoldMember 物件,而這個字串也可以來自資料庫的欄位,這樣一來就能靈活解決之前的問題了。當然你可以用它配合抽象工廠模式,傳入要實作的工廠名稱,來解決『一系列物件』的問題。
P.S.反射並不是每一種程式語言都支援,並且效能上需要付出相對代價,詳細使用方式請善用搜尋大法。