l

2014年4月16日 星期三

重新整理Factory Method Pattern,Take 2

Mar. 19 09:10~10:50

螢幕截圖 2014-03-19 10.47.07

 

這幾年心中有一個心願,想要寫一本Design Pattern的書,先談談Alexander的Pattern理論,再依據context、problem、force、solution、resulting context等元素,把GoF Design Patterns這本書裡面所介紹的23的pattern重新整理一遍。希望經過這樣的整理,可以讓鄉民們用不同於現有大部分文獻、書籍的角度,以比較「系統化」且相對「輕鬆」的方式,來學習與重新檢視pattern。

結果2012年先出版了《笑談軟體工程:敏捷開發法的逆襲》,而第二本書《笑談軟體工程:例外處理設計的逆襲》也預計即將在今年四月出版。但是這個「整理pattern」的工作卻比想像中要來的艱鉅,到現在八字都還沒一撇。

鄉民甲:GoF Design Patterns都出版快20年了,網路上的文章、市面上的書也那麼多,何難之有?

困難的地方在於:

  1. Alexander的Pattern理論:Teddy在部落格上面談了好幾次關於Alexander的Pattern理論,還有它對於「到底什麼是設計」的看法。這個問題乍看之下很抽象,過於理論,好像沒什麼實用價值。但對於一個「(軟體)設計師」而言,弄清楚這種抽象觀念的助益卻是難以想像的大。弄懂Alexander的Pattern理論需要一些功夫,弄懂之後再用鄉民聽得懂的方式轉述一次,又是另外一種挑戰。
  2. 重寫每一個GoF Design Pattern所謂「重寫」的意思不是找個看起來很有趣且通俗的「程式範例」來解釋pattern,而是要用context、problem、force、solution、resulting context的格式來「重新包裝」這23個pattern。不管是哪一本書,只要是教導GoF的23個design pattern,提到每一個pattern定義的時候,一定會列出GoF書中關於pattern的intent(意圖)。如果鄉民們仔細閱讀GoF書中關於每一個pattern的intent,這些內容的敘述,大部分是指solution,而pattern的problem與形成這些problem的force則是散布在其他內容之中,要靠自己慢慢去「提煉」。鄉民們可能都知道pattern是一個problem-solution pair(一個pattern包含一個問題的解法),學過pattern的各位,對於每一個pattern的解法必定非常熟練,但有辦法很清楚地用一句話說出每一個pattern的解決什麼「問題」嗎?隱隱約約好像知道,但卻又不太容易說明白。如果可以把GoF的每一個pattern重新整理一遍,對於學習、套用design pattern,會有很大的助益。

***

2013年8月花了點時間重新整理了幾個pattern:

最近對於Alexander的pattern理論又有一些比較不同的想法,所以想要把這些看法加入「Design Pattern這樣學就會了」的課程教材,因此又回頭看了一下之前整理的pattern,順便看看有沒有可以改進的地方。看到之前整理的Factory Method,覺得context寫得不好,想要修改一下,沒想到這個「修改一下」的念頭,居然花了兩整天的時間。

以下是修改前的內容:

***

Name:Factory Method (修改前)

Context:許多物件導向程式語言提供我們用new表示式來產生物件,但並非所有的物件都可以簡單地透過new來產生。

Problem:如何產生一個物件?

Force:

  • 你有一群繼承自相同介面的產品物件,
    • 你想要將產生哪一個具體產品物件的責任交由子類別來決定,可能是因為在編譯期間你並不知道要產生哪一個具體類別,又或者是你想要提供子類別產生具體產品類別的彈性。
    • 你允許客戶端傳入參數來選擇所要產生的具體類別。
  • 物件產生之後尚須經過適當的設定步驟才可以傳回給客戶端使用,如果可以將產生物件的過程從客戶端抽離出來,則可以避免客戶端產生重複的程式碼。

Solution:將產生物件的詳細步驟封裝在一個factory method裡面,讓客戶端程式透過這個factory method來得到新的物件。

***

以下是修改後的內容:

Name:Factory Method (修改後)

Context: 你有一個抽象的「產品介面」,該介面通常有多種不同的具體實作,稱為「具體產品」。你的程式將會使用到這些「具體產品」。

Problem: 如何產生物件?

Force:

  • 在設計階段(編譯期間)你並不知道會使用哪一個具體產品類別。
  • 需要依賴另外一個物件來決定要產生哪一種型別的具體產品物件。
  • 讓客戶端直接產生具體產品物件是最簡單的方式,但會造成兩者關係過於緊密(耦合性太高),限制日後替換具體產品物件的彈性。
  • 你可以套用Simple Factory,把產生哪一種「具體產品」類別的條件式判斷集中在一起(套用Simple Factory,)。但是只要有新的「具體產品」類別加入,你還是必須修改Simple Factory的條件式。

Solution: 將產生物件的過程封裝在物件的某個函數(factory method)裡面,客戶端程式必須透過這個函數來取得物件。新增建構者(Creator)類別,將負責產生具體產品物件的責任交由它負責。在建構者身上定義一個專門回傳「具體產品」物件的函數(Factory Method)以實作此責任。

透過繼承,建構者的子類別可以覆寫Factory Method,以便傳回特定的具體產品物件。在這種情況下,一個具體建構者绑定一個具體產品。建構者也可以透過泛型程式設計(generic programming)技巧,來實作Factory Method,以取代僅是為了覆寫Factory Method就必須新增建構者子類別的做法(減少不必要的建構者子類別數量)。

***

關於Factory Method其實還有很多東西可以說,以後有機會再談。最後只說一點,在GoF的書中,Factory Method的定義(意圖)是:

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

這個定義裡面,有一句話「let subclasses decide which class to instantiate」(讓子類別決定產生哪一個類別的實例)剛開始Teddy在整理Factory Method的時候,被這句話困擾許久。因為「讓子類別決定產生哪一個類別的實例」只是一種實作Factory Method的方法,並非導致形成Factory Method這個solution的關鍵force,真正影響Factory Method的force另有其人。但是這句話又被放在Factory Method的意圖裡面,應該不能忽視它才對啊挑眉質疑。最後思考了許久才再度體會到「盡信書不如無書」這件事,不必拘泥於原本書中對於每一個pattern意圖的描述。

仔細看GoF的書,在Factory Method的實作敘述中就提到可以不透過繼承實作Factory Method的方法。以前讀書的時候沒觀察到,書中對於Factory Method意圖的描述其實還「不夠抽象」。如果改成這樣也許會比較清楚一些:

Define an interface for creating an object, but let its clients decide which class to instantiate. Factory Method lets a class defer instantiation to its clients.

這個client,可以是subclass,也可以是透過composition關係來使用Factory Method的其他物件。

***

友藏內心獨白:寫書搞得好像要出paper一樣,也太累了吧挑眉質疑

2 則留言:

  1. 這個...寫程式真的有必要搞成這樣嗎?@@
    把握模組化的原則(OCP, SRP...),當要給user決定適當instance時,如果發現產生的動作須要修改多個地方或不可能作到時,自然就要將產生的動作集中起來,或以語言的機制(inheritance, runtime class creation, prototype...)去實現,不同的實作的優缺點再細部拿出來比較一下,不就好了嗎?

    回覆刪除
  2. 承上,個人也覺得不需要,不過多學點東西充實自己的武器庫也不錯啦,避免書到用時方恨少的窘境。

    回覆刪除