1.1 模式解說
策略(Strategy)模式的用意是定義一組算法(algorithms),並將每個算法封裝到具有共同接口的獨立的類中,從而使它們可以相互替換。策略模式讓算法變化獨立於使用它的客戶端。
要了解策略模式的使用動機和意義,我們得先從一個有趣的例子說起。在一個物料管理系統中,出庫和入庫模塊是該系統的核心部分(下面我們以出庫為例進行分析)。
對於一個沒有面向對象編程經驗的程序員,他們往往會把出庫的所有邏輯都放在客戶端(出庫單界面),並在客戶端利用條件分支語句來判斷該出庫單類型是領料、借料還是報損,以便選擇不同的出庫結算方法,如圖 1‑1所示。這樣一來,客戶端的代碼就變得復雜和難以維護。比如:需要新增調撥單類型的出庫時,就要修改判斷條件,重新編譯和發布客戶端。當情況愈來愈復雜,條件分支會愈來愈多,添加的程序代碼也會愈來愈多,這樣讓客戶端愈來愈大並難以維護,互相影響和出錯的可能性增大。
圖 1‑1 基於面向過程思想設計的出庫模塊
如果用面向對象的思想來分析,可以把領料單、借料單、報損單看作是出庫單的派生類,如圖 1‑2所示。這樣出庫單作為單據基類提供單據的共同接口,而利用繼承的辦法在子類裡實現不同的出庫行為。這實際上利用了面向對象裡的一個重要概念:多態。
但是這樣的設計還有美中不足的地方,這就是環境和行為緊密耦合在一起。也就是說,單據和具體出庫的算法緊密耦合在一起。強耦合使得兩者不能獨立演化,限制了重用性和擴展性。
圖 1‑3是利用策略模式重新設計的出庫模塊。出庫單據對象通過一個出庫操作對象(即策略模式中的Context)來引用出庫策略對象。各種具體的出庫策略則由出庫策略類的派生類實現。出庫單據可以由出庫操作和單據樣式分別提供出庫結算方法和單據顯示界面。這樣,策略模式就把出庫的行為從出庫單據的環境中獨立出來,出庫算法的增減、修改都不會影響到環境和客戶端。
圖 1‑2基於面向對象思想設計的出庫模塊
圖 1‑3基於設計模式思想設計的出庫模塊
策略模式的優勢在於算法和環境的分離,兩者可以獨立演化。為了更好地說明算法和環境分離的好處,我們不妨看一下圖 1‑4的設計。在這個設計中,已經沒有出庫和入庫模塊的概念,因為我將所有出/入庫單據抽象出來,在運行期動態組合單據的界面和行為。通過出/入庫操作類,可以維護、查詢、配置不同的行為類。抽象出的出/入庫行為以策略類的方式封裝了其對應的算法,以便完成不同類型的出入庫單據的操作。這就顯而易見地提高了系統的重用性和可擴展性,減低維護的難度。
圖 1‑4 策略模式的優勢在於算法和環境的分離,兩者可以獨立演化
由此可見,策略模式適用於以下情形:
· 當許多相關的類之間的差異只在於其行為時。策略模式可以動態地讓一個對象在許多行為中選擇一種行為。
· 當實現一個目的有多種可選算法時,比如:你出於不同的利弊權衡考慮定義的那些算法(即相當於應用不同的策略)。這些具體的算法可以封裝成抽象算法類的派生類,並享用該抽象算法類的統一接口。通過多態性,客戶端只要持有一個抽象算法類的對象,就可以選用任何一個具體的算法。
· 當一個算法使用的數據不可以讓客戶端得知時。使用策略模式可以避免暴露復雜的與算法相關的數據結構。其實客戶端也沒有必要知道這些與算法相關的知識和數據。
· 當一個類定義有很多行為,且用多個條件語句來判斷選擇這些行為時。策略模式可以把這些行為轉移到對應的具體策略類中,從而避免了難以維護的多重條件選擇,體現了面向對象的編程思想。
1.2 結構與用法
策略模式的結構如圖 1‑5所示,它包括了以下參與者:
· 抽象策略(TStrategy)——為所有支持的算法聲明一個共同的接口。TContext使用這個接口調用由TConcreteStrategy定義和封裝的算法。
· 具體策略(TConcreteStrategy)——封裝了具體算法或行為。實現TStrategy接口。
· 上下文(TContext)——持有一個到TStrategy的引用。調用TStrategy接口,動態配置具體算法或行為。
圖 1‑5策略模式的結構
在策略模式中,通過TStrategy和TContext的交互實現所選擇的算法。當算法被調用時, TContext可以將該算法所需要的所有數據都傳遞給該TStrategy。或者,TContext可以將自身作為一個參數傳遞給TStrategy操作。
當TContext將客戶端請求轉發給它的TStrategy時,客戶通常創建並傳遞一個TConcreteStrategy對象給該TContext;這樣, 客戶端僅與TContext交互。通常有一系列的TConcreteStrategy類可供客戶端從中選擇。