[Head First設計模式]策略模式


系列文章

[Head First設計模式]山西面館中的設計模式——裝飾者模式

[Head First設計模式]山西面館中的設計模式——觀察者模式

[Head First設計模式]山西面館中的設計模式——建造者模式

[Head First設計模式]餃子館(冬至)中的設計模式——工廠模式

[Head First設計模式]一個人的平安夜——單例模式

[Head First設計模式]搶票中的設計模式——代理模式

引言

該過年了,總讓人有點浮躁,公司就省倆人了,唉,如果坐等時間,那實在難熬,只能給自己找點事做,轉移一下注意力。進入今天的主題吧策略模式。

策略模式定義

策略模式定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變化獨立於使用算法的客戶。

書中鴨子的例子

模擬鴨子的簡單應用

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 }
Duck
 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 }
IFlyBehavior
 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 }
IQuackBehavior
 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 }
FlyWithWings
 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 }
FlyNoWay
 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 }
MuteQuack
 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 }
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 class Squeak : IQuackBehavior
10     {
11         #region IQuackBehavior 成員
12 
13         public void Quack()
14         {
15             Console.WriteLine("吱吱叫.......");
16         }
17 
18         #endregion
19     }
20 }
Squeak
 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 }
MallardDuck
 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 }
WoodDuck
 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)。這種作法和繼承不同的地方在於:鴨子的行為不是繼承而來,而是和適當的行為對象組合而來。

第三個設計原則

多用組合,少用繼承

使用組合建立系統具有很大的彈性,不僅可將算法族封裝成類,更可以在運行時動態地改變行為。

優缺點

優點:
1、 策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行為族。恰當使用繼承可以把公共的代碼轉移到父類里面,從而避免重復的代碼。
2、 策略模式提供了可以替換繼承關系的辦法。繼承可以處理多種算法或行為。如果不是用策略模式,那么使用算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的算法或行為。但是,這樣一來算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動態改變算法或行為變得不可能。
3、 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個多重轉移語句里面,比使用繼承的辦法還要原始和落后。
缺點:
1、客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用於客戶端知道所有的算法或行為的情況。
2、 策略模式造成很多的策略類,每個具體策略類都會產生一個新類。有時候可以通過把依賴於環境的狀態保存到客戶端里面,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用。換言之,可以使用享元模式來減少對象的數量。
參考書
Head first 設計模式
沒想到該回家了,還能耐住性子看書,這里將書上的例子整理了一下,比較好理解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM