你可能看了好幾個教學,但就是覺得好像抓不到某個 Design Pattern 的要領?這系列文應該可以幫助到你。
那麼這系列會有什麼不同呢?
通常 Design Pattern 都會用 Class diagram 來描述結構,然後以文字來輔助說明。這系列文我會直接用 type system 來做說明。
Type System 可以算是一種邏輯表示方法。跟在學校學的邏輯符號不一樣的地方是,它更靠近程式語言,所以他同時有邏輯符號的嚴謹性跟程式語言的易用性。另外現在很多程式語言都有支援不錯的 type system,不過語法上可能會有些差異。為了方便理解,我也會附上 TypeScript 的範例來比較。
讓我們先來舉幾個簡單的例子,以下是常見的 type:
String
– 字串Int
– 整數Double
– 浮點數[String]
– 字串陣列然後我們可以用這些來標示某個物件或是變數的 type,例如:
雙冒號的左邊是字串物件,右邊是標示「左邊的物件是什麼 type」
這些幾乎在所有程式語言都有類似的 type,基本上只有名字會不一樣。但其實 type 可以再更複雜/抽象一點,例如:
Car
Animal
Bar
如果你看過一些 OOP 或 Design Pattern 的教學的話,這些應該都算是常提到的 class name。是的,基本上 OOP 的 class 就是一種 type。
跟 OOP 不一樣的是,還有一種常用 type 不常被 OOP 相關文章書籍提到:function type。例如:
這代表一個有一個 Int 為 input 的 function,而其 return type 是 Int。你可以很輕易地想出一些例子,譬如說 addOne
, minusOne
。
這個代表一個 function 叫做 addOne,而它吃一個 Int 為參數,回傳一個 Int。
要表達 function invocation 的話,可以寫成這樣
這樣代表,把 3 塞進 addOne function 當 input
另一個例子:把整數轉成字串
不熟悉這個形式的話,只要記得以下幾點就好:
::
,那就代表是描述左邊的 value 是右邊的 type。上面的例子來說的話。toString
是一個 value,它的 type 是 Int -> String
。(沒錯,function 也是一種 value)當然 function 應該要可以有多個參數:
(參數跟參數中間直接用空格格開就好)
更多例子:
Function type 其實沒有什麼特別的,跟 Int 一樣,它也只是一種 type。所以你可以在 function type 裡面塞 function type
注意有個括號,所以這個 map function 的第一個參數其實是 (Int -> Int)
,也就是前面提到的其中一種 function type。
為了方便,我們可以為 type 取別名:
這樣我們就可以把上面的 map function type 改成
這樣就很清楚整個 Int -> Int
是一個參數了
使用上則是:
對照 TypeScript
以下的 statement 是不合法的:
然後,以上面的 map 為例,這樣也是不合法的:
FuncInt1
不是 value ,所以不能餵進 map function 當第一個參數
在有些狀況下,我們需要宣告一個全新的 type,就跟在 Java 裡面我們可以宣告一個新的 class 一樣:
這樣就是宣告了一個叫做 Animal 的 type。
你可能會説「一直跟我說 type,給我看實作啊!」但實作細節應該是「封裝」好的,所以要理解這個程式結構,應該是不需要看實作的。這也是 OOP/Design Pattern 常常提到的觀念。舉例來說,假設你要實作一個酒吧結帳系統(借用 wiki Strategy Pattern 範例),那麼你會需要計算一筆「單」要收多少錢,那麼我們只需要知道有 "Order" 的 type 跟 getPrice
這個 function 就行了,用上面介紹的表示方法就是:
介紹完 type system,接下來我們就可以來討論如何用這個工具來理解 Strategy Pattern 了!
延續上面講的,結帳的計算功能,假設這間店有 happy hour 時段,在這個時段內有些東西半價,那在計算的時候就要根據不同時段來採用不同的計算「策略」。簡單的做法可能是像這樣:
也就是根據現在是什麼時段來決定使用那個 function。雖然簡單,但這個做法有個問題是:假設今天 happy hour 的時段有分好幾種呢?例如 Happy Friday、Crazy Saturday,那我們是不是每次有新的 happy hour 就要多寫一個 get price function 呢?這樣應該會有不少重複的邏輯吧?
所以我們可以把計算單個 OrderItem 計算抽出來,也就是 getTotalPrice 的實作不會包含 item price 計算,把這個計算委託給另外指定的 function。那這個「委託」應該要怎麼做呢?很簡單,讓「OrderItem 的價錢計算」變成一個參數就好。
在這個版本中,我們加入了新的參數:一個可以把 OrderItem 轉成整數的 function。這樣我們就可以在不修改 getTotalPrice 的情況下抽換 item price 的計算了!
基本上一個物件或函數,其中的重要邏輯是由 caller 遞進去的,我們就可以稱他為 Strategy Pattern。而它帶來的好處是:可以更高程度的共用邏輯,並且可以動態的抽換邏輯,例如上述 getTotalPrice
中,OrderItem
的價錢計算是在最後面才「置入」的。
在 Class Diagram 中,首先你需要理解什麼是 Class,然後理解什麼是繼承或是介面,然後畫出三個方框跟一些箭頭來表示他們之間的靜態關係,但並沒有描述它們之間會怎麼動態互動。
用 Type System 的話,我們可以用短短的一行來描述:要做這樣的計算,我們需要一個「計算 OrderItem 的價錢的方法」跟 Order。在同樣不描述實作細節的情況下,這個方法更能夠讓 dependency 變得更明顯。
Strategy Pattern 應該算是簡單的 Design Pattern,所以你可能會認為 Type System 的這種表示方法並沒有帶來太多的差異。我保證其他更複雜的就會更明顯了。接下來我會用這種方式來帶大家重新理解其他的 Design Pattern。