今天要討論的是 Strategy 模式。這個模式會分做兩篇。
在今天這一篇,我首先先提出一個案例。遇到需求時,我們有什麼可能的解決方式?最後,我們在引入策略模式先概念性地去解決這個問題。
下一篇我們再實作出策略模式的簡單的程式碼,最後再深入去談策略模式的特徵。
我們今天有一個台灣某個電子商務公司的訂單處理系統,這個系統必須要能處理不同國家的訂單。
這個系統的總架構是:Client 丟出處理銷售的請求時,會先進到一個 TaskController
物件裡做處理。它協助確認當請求出現時,它會將請求往下轉發給 SalesOrder
物件進行訂單處理。如下圖。
SalesOrder
物件的功能包括:
有些功能可能需要其他物件實作。例如,SalesOrder
物件沒有必要自己列印,它可以呼叫 SalesTicket
物件來列印。
架設我們有了新的需求:修改處理稅額的方法。例如,我們現在必須處理台灣之外的顧客的訂單稅額。
作者提到了幾種做法:
我們一一分析這些做法。
複製貼上是最要不得的辦法,這會使專案維護者需要維護多份相似的程式碼。
這個方法一開始看起來合理,但是當 case 變多的時候,會讓整個程式碼過於冗長且不從容;而且假設今天需要加入加拿大的稅率計算,但是我們知道加拿大有英語區與法語區,兩區的稅率假設計算方式需要不同,那麼原本的 switch-case 裡又要再多加上一個 if 判斷。
多條直線分支又在四散,作者稱之為「分支蔓延 (switch creep) 」。
當繼承是使用 DAY8 的「類別再利用」方法。舉例來說,對於美國銷售訂單,我們可以從 SalesOrder
衍生出一個 AmericaSalesOrder
,在裡頭再覆寫出自己的稅額處理規則,如圖所示。
但是這樣的解決方式會使我們建構的繼承層次無法靈活因應其他可能的變化。
同時,當衍生類別(愈來愈多國家的稅額處理)愈來愈多,容易產生太深的繼承層次,最終會讓程式碼難以理解,從而產生弱內聚、存在冗餘,和不容易測試。而這都是我們不想要的。
我們必須考量「程式設計中什麼應該是可變的」、「對變化的概念進行封裝」,而且該「優先使用物件聚合,而不是類別繼承」。因此,我們會如此做:
根據這個策略,我們將繳稅規則 (CalTax
) 封裝起來:
接下來,再用聚合取代繼承:
這就是 strategy 模式的一個實踐。這個圖有沒有很熟悉呢?沒錯,在 DAY8 的例子有出現過唷!
更詳細的內容我們明天再繼續!