系列文章
引言
該過年了,總讓人有點浮躁,公司就省倆人了,唉,如果坐等時間,那實在難熬,只能給自己找點事做,轉移一下注意力。進入今天的主題吧策略模式。
策略模式定義
策略模式定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變化獨立於使用算法的客戶。
書中鴨子的例子
模擬鴨子的簡單應用
Joe上班的公司做了一套相當成功的模擬鴨子游戲SimUDuck,游戲中出現各種鴨子,一邊游戲戲水,一邊呱呱叫。此系統的內部設計使用了標准的OO技術,設計了一個鴨子超類,並讓各種鴨子繼承此超類。
讓鴨子能飛
去年,公司的競爭力加劇,公司主管認為該是創新的時候了。主管認為,此模擬程序需要會飛的鴨子,將競爭者拋在后面。
改進繼承
Joe認識到繼承可能不是一個好的解決辦法,因為他剛剛拿到來自主管的備忘錄,希望以后每六個月更新產品(至於更新辦法,他們還沒想到)。Joe知道規格會常常改變,每當有新的鴨子子類出現,他就要被迫檢視並可能需要覆蓋fly()和quack().....這簡直是無窮盡的噩夢。所以,他需要一個更清晰的方法,讓某些(而不是全部)鴨子類型可飛或可叫。
其實,並非所有的鴨子子類都具有飛行和呱呱叫的行為,所以繼承並不是適當的解決方式。雖然Flyable與Quackable可以解決一部分的問題(不會再有會飛的橡皮鴨),但是卻造成代碼無法復用,這只能算是從一個噩夢跳進另一個噩夢。甚至,在會飛的鴨子中,飛行的動作可能還有多種變化......
現在我們知道使用繼承有一些缺失,因為改變鴨子的行為會影響所有種類的鴨子,而這並不恰當。Flyable與Quackable接口一開始似乎還挺不錯,解決了問題(只有會飛的鴨子才繼承Flyable),但是接口不具有實現代碼,所以繼承接口無法達到代碼的復用。這意味着:無論何時你需要修改某個行為,你被迫得往下追蹤並修改每一個有定義此行為的類,一不小心,可能造成新的錯誤。
第一個設計原則
設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。這個概念很簡單,幾乎是每個設計模式背后的精神所在,所有的模式都提供了一套方法讓系統中的某部分改變不會影響其它部分。
分開變化和不變化的部分
為了要分開變化和不變化的部分,我們准備建立兩組類,一個是fly相關,一個是quack相關的,每一組類將實現各自的動作。比如說,我們可能有一個類實現“呱呱叫”,另一個類實現“嘰嘰叫”,另一個類實現“安靜”。
設計鴨子行為
我們利用接口代表每個行為,比方說,IFlyBehavior與IQuackBehaivor,而行為的每個實現都必須實現這些接口之一。所以這次鴨子類不會負責實現fly與quack接口,而是由其他類專門實現IFlyBehavior與IQuackBehaivor,這就稱為“行為類”。由行為類實現行為接口,而不是由Duck類實現接口。
這樣的做法迥異於以往,以前的做法是:行為是繼承自Duck超類的具體實現而來,或是繼承某個接口並由子類自行實現而來。這兩種做法都是依賴於實現,我們被實現綁的死死,沒辦法更改行為(除非寫更多的代碼)。
第二個設計原則
針對接口編程,而不是針對實現編程。
關於接口編程和實現編程:
假設有一個抽象類Animal,有兩個具體的實現(Dog與Cat)繼承自Animal。
針對實現編程的作法如下:
1 Dog d = new Dog(); 2 d.bark();
針對接口/超類型編程作法如下:
1 Animal animal = new Dog(); 2 animal.makeSound();
子類型實例化的動作是“在運行時才指定具體實現的對象”
1 a = getAnimal(); 2 a.makeSound();
實現鴨子的行為
這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象復用,因為這些行為已經與鴨子無關了。而我們可以新增一些行為,不會影響到既有的行為類,也不會影響有使用到飛行行為的鴨子類。
集成鴨子的行為
鴨子現在會將飛行和呱呱叫的動作,委托(delegate)別人處理,而不是使用定義在自己類(或子類)內的方法。
① 首先,在鴨子中加入兩個實例變量, 分別為FlyBehavior與QuackBehavior,聲明為接口類型(而不是具體類實現類型),每個變量會利用多態的方式在運行時引用正確的行為類型(例如:FlyWithWings、Squeak . . . 等)。
我們也必須將Duck類與其所有子類中的fly() 與quack( ) 移除,因為這些行為已經被搬移到FlyBehavior與QuackBehavior類中了。我們用performFly()和performQuack()取代Duck類中的fly()與quack()。
代碼測試

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public abstract class Duck 10 { 11 //為行為接口類型聲明兩個引用變量,所有鴨子子類都繼承它們。 12 public IFlyBehavior flyBehavior; 13 //每只鴨子都引用實現QuackBehavior接口的對象。 14 public IQuackBehavior quackBehavior; 15 public Duck() { } 16 public abstract void Display(); 17 public void PerformFly() 18 { 19 //委托給行為類 20 flyBehavior.Fly(); 21 } 22 public void PerformQuack() 23 { 24 //鴨子將呱呱叫行為委托給quackBehavior引用的對象。 25 quackBehavior.Quack(); 26 } 27 public void Swim() 28 { 29 Console.WriteLine("會游泳....."); 30 } 31 } 32 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public interface IFlyBehavior 10 { 11 //所有飛行行為必須實現的接口。 12 void Fly(); 13 } 14 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public interface IQuackBehavior 10 { 11 void Quack(); 12 } 13 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class FlyWithWings : IFlyBehavior 10 { 11 #region IFlyBehavior 成員 12 13 public void Fly() 14 { 15 Console.WriteLine("會飛......"); 16 } 17 18 #endregion 19 } 20 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class FlyNoWay : IFlyBehavior 10 { 11 #region IFlyBehavior 成員 12 13 public void Fly() 14 { 15 Console.WriteLine("不會飛......"); 16 } 17 18 #endregion 19 } 20 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class MuteQuack : IQuackBehavior 10 { 11 #region IQuackBehavior 成員 12 13 public void Quack() 14 { 15 Console.WriteLine("不會叫"); 16 } 17 18 #endregion 19 } 20 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class Quack : IQuackBehavior 10 { 11 #region IQuackBehavior 成員 12 13 void IQuackBehavior.Quack() 14 { 15 Console.WriteLine("呱呱叫......"); 16 } 17 18 #endregion 19 } 20 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class Squeak : IQuackBehavior 10 { 11 #region IQuackBehavior 成員 12 13 public void Quack() 14 { 15 Console.WriteLine("吱吱叫......."); 16 } 17 18 #endregion 19 } 20 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 /// <summary> 10 /// MallardDuck從Duck繼承,具有flyBehavior 和quackBehavior 實例變量。 11 /// </summary> 12 public class MallardDuck : Duck 13 { 14 public MallardDuck() 15 { 16 //FlyWithWings作為IFlyBehavior類型 17 base.flyBehavior = new FlyWithWings(); 18 //Quack類處理呱呱叫。 19 base.quackBehavior = new Quack(); 20 } 21 public override void Display() 22 { 23 Console.WriteLine("綠頭鴨......"); 24 } 25 } 26 }

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 public class WoodDuck : Duck 10 { 11 public WoodDuck() 12 { 13 flyBehavior = new FlyNoWay(); 14 quackBehavior = new MuteQuack(); 15 } 16 public override void Display() 17 { 18 Console.WriteLine("木頭鴨子....."); 19 } 20 } 21 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Wolfy.設計模式 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Duck mallard = new MallardDuck(); 14 mallard.Display(); 15 //調用MallardDuck繼承來的perform方法,進而將綠頭鴨的行為委托給quack和fly的行為類來處理。 16 mallard.PerformFly(); 17 mallard.PerformQuack(); 18 mallard.Swim(); 19 Duck wood = new WoodDuck(); 20 wood.Display(); 21 wood.PerformFly(); 22 wood.PerformQuack(); 23 wood.Swim(); 24 Console.Read(); 25 26 } 27 } 28 }
結果
總結
所有鴨子從Duck繼承,飛行行為實現FlyBehavior接口,呱呱叫行為實現QuackBehavior接口。
“有一個”(has a)可能比“是一個”(is a)更好
有一個關系相當有趣:每一鴨子都有一個FlyBehavior且有一個QuackBehavior,讓鴨子將飛行和呱呱叫委托它們代為處理。
如果將兩個類結合起來使用(如同本例),這就是組合(Composition)。這種作法和繼承不同的地方在於:鴨子的行為不是繼承而來,而是和適當的行為對象組合而來。
第三個設計原則
多用組合,少用繼承
使用組合建立系統具有很大的彈性,不僅可將算法族封裝成類,更可以在運行時動態地改變行為。
優缺點