在上一個章節有提到設計模式的原則,這一章主要會提到以下三點:
1.找出程式會變的東西,把其和不會變的部分分開。
2.針對介面寫程式,而不是針對實作寫程式。
3.多用組合,少用繼承。
假設我們正在開發遊戲一個遊戲,並且以物件導向的方式創造了一個角色:鳥(Bird),讓其他種鳥的角色可以繼承:
此時,為了增加角色功能的豐富程度,我們需要添加了飛行功能(fly),所以把飛行功能增加到鳥的類別上,讓繼承鳥類別的角色都可以一併繼承這個功能。
過了一段時間,我們發現了一個問題:當我們創建了新角色鴕鳥(ostrich)的時候,它竟然會飛!這似乎並不符合常理。
所以為了修改這個錯誤問題,把鴕鳥的飛行功能覆寫(override)掉,應該就解決了...
過了一段時間,當新角色玩具鳥(ToyBird)出現時又遇到相同的問題,它既不會飛,也不會叫,所以再次覆寫飛與叫的方法:
此時我們覺得,每次建立一個新角色,就要重新檢查繼承而來的方法是否合理,並且視情況覆寫,這個做法有點糟,
這時候我們想到,可以把飛(fly)跟叫(Scream)拿出來,變成介面,讓會飛或會叫的鳥去實作那個介面就好,不會飛或叫的角色就可以不用實作。
沒想到,過了一陣子,我們發現這樣的做法,會讓我在創造新的角色時,有可能需要寫出跟其他角色重複的程式碼:因為很多角色的飛行模式是一樣的!更糟的是,一旦我在遊戲內修改一種飛行模式的定義,我就必須確認所有跟這個飛行模式相關的類別並修改他們的程式碼。
怎麼辦?這個時候我們需要從第一個設計模式原則下手:
1.找出程式會變的東西,把其和不會變的部分分開。
把會變的部分封裝起來,之後如果需要進行修改,就針對要變的部分進行修改,使其不會影響其他部分。
從上面的案例我們可以看到,會變的部分就是兩種行為:飛與叫,因此,我們需要建立兩組類別,把他跟原來鳥的類別分開。每一組類別都去實作不同的行為。
接著,我們可以在角色初始化的時候,指派這些行為給不同種類的鳥,甚至可以建立SetBehavior的方法,來在程式執行的期間,可以動態的調整這些角色的行為。
public class ToyBird : Bird
{
public ToyBird()
{
//不會飛也不會叫
FlyBehavior = new FlyWithNothing();
ScreamBehavior = new MuteScream();
}
}
//傳入新的飛行行為,替換舊有行為
public void SetFlyBehavior(FlyBehavior flybehavior)
{
FlyBehavior = flybehavior;
}
//傳入新的叫法,替換舊有行為
public void SetScreamBehavior(ScreamBehavior screamBehavior)
{
ScreamBehavior = screamBehavior;
}
這個做法與之前的作法差別在於:之前的行為都是為了鳥這個超類別,或是鳥的子類別去量身打造的實作方法,因此這些行為都依賴一個實作,導致需要改變行為的時候,只能透過修改程式來達成,且會隨著類別擴大而造成維護上的困難。
在新的設計中,鳥的子類別會使用介面所代表的兩種行為。而這些行為的實作,是單純為了實作飛(fly)跟叫(Scream)的具體行為,也就不會被限制在鳥的子類別裡面。
而這樣的設計,就符合了設計原則的第二點:
2.針對介面寫程式,而不是針對實作寫程式。
此外,把類別組合起來的方式,就是組合,用組合建立起的系統可以將行為進行封裝,並且可以在執行起改變行為,我們可以從此看到第三種原則:
3.多用組合,少用繼承。
以上就是策略模式的運用,策略模式的定義就是去封裝一系列的演算法(可以把那些實作的行為視為演算法),並且在不影響用戶端的情境下替換並改變演算法。
Reference:
深入淺出設計模式-設計模式入門, 2/e (Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software, 2/e)
謝謝
你是設計模型觀念,寫得最清楚的
(有圖有差XD)
謝謝XD PO文的難度在於 畫圖要話很久 簡短寫又只有自己看得懂