Head First設計模式之策略模式(Strategy Pattern)

前言:

剛剛開始學習設計模式,以前也接觸過一些,可是歷來都沒有系統的學過,此次打算好好的學習一下。這裏就當是對學習過程的一個記錄、整理,以即可以在之後不時的溫故知新。算法

這一節採用一個鴨子的示例,層層推動,引入策略模式。具體以下:編程

1.   基本需求:建立有一些特性的鴨子

鴨子擁有以下的一些特性:游泳戲水、呱呱叫、外觀設計模式

初步實現鴨子的特性:ide

鴨子超類:學習

public abstract class Duck
    {
        public void Quack()
        {
            Console.WriteLine("鴨子叫:呱呱呱");
        }
        public void Swim()
        {
            Console.WriteLine("鴨子游泳");
        }
        public abstract voidDisplay();
    }

鴨子子類:測試

public class MallardDuck:Duck
    {
        public override voidDisplay()
        {
            Console.WriteLine("綠頭鴨");
        }
    }
 
    public class RedHeadDuck : Duck
    {
        public override voidDisplay()
        {
            Console.WriteLine("紅頭鴨");
        }
    }

2.   新的需求:讓鴨子飛

2.1 在Duck類中添加飛的方法

以下:設計

 public abstract class Duck
    {
        public void Quack()
        {
            Console.WriteLine("鴨子叫:呱呱呱");
        }
        public void Swim()
        {
            Console.WriteLine("鴨子游泳");
        }
 
        public void Fly()
        {
            Console.WriteLine("鴨子飛了");
        }
        public abstract voidDisplay();
    }          

2.2 並不是全部的鴨子都能飛

這個時候會帶來一下新的問題:orm

並不是全部的鴨子都能飛,好比:橡皮鴨對象

並不是全部的鴨子都能呱呱叫:好比:橡皮鴨blog

對應的解決辦法:

2.3 覆蓋鴨子超類中的Fly和Quack方法

經過在橡皮鴨的實現中覆蓋鴨子超類中的Fly和Quack方法

以下:

public class RubberDuck : Duck
    {
        public new void Fly()
        {
            //Do nothing
        }
 
        public new void Quack()
        {
            Console.WriteLine("鴨子叫:吱吱吱");
        }
}

2.4 有些鴨子既不能飛也不能叫

這樣帶來的新的問題:

好比木頭鴨,既不會游泳 也不會叫

若是直接繼承自Duck超類,還須要對Fly和Quack方法進行重寫。

這個時候咱們可能意識到繼承不是解決問題的最終辦法,由於每個繼承自鴨子的子類都須要去被迫的檢查而且可能要覆蓋Fly和Quack方法。

咱們可能只須要讓某些鴨子具備Fly和Quack便可

2.5 經過接口讓某些鴨子具備Fly和Quack特性

經過接口來解決此問題

以下所示:

public interface IFlyable
    {
        public void Fly();
    }
 
    public interface IQuackable
    {
        public void Quack();
}
 
public class MoodDuck : IFlyable, IQuackable
    {
        public void Fly()
        {
            //Can't Fly
        }
 
        public void Quack()
        {
            //Can't Quack
        }
    }

經過接口的方式雖然結局了一部分鴨子不會飛或者不會叫的問題,

2.6 針對實現編程,代碼不能複用

使用接口產生新的問題:

代碼不能複用,產生過多的重複代碼。若是你想要修改某個行爲,必須在買個定義了此行爲的類中修改它,這樣極可能形成新的錯誤。

2.7 封裝變化,針對接口編程

這個時候出現了第一個設計原則:

找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼放在一塊兒。

即:

把會變化的部分抽取並封裝起來,以便之後能夠輕易的改動或者擴充此部分,而不影響其餘不變的部分。

分開變化和不會變化的部分,在本例中Fly和Quack會隨着鴨子的不一樣而改變,因此咱們要將其抽離出Dock類,並創建一組新類來表明每一個行爲。

這時出現了設計的另外一個原則:

針對接口編程,而不是針對實現編程。

這裏咱們用接口表明每一個行爲,例如:IFlyable和IQuackable,每一個行爲的實現都將實現對應的接口。

因此鴨子類不會負責實現IFlyable和IQuackable接口,而是由一組其餘專門的類來實現對應的接口,稱之爲「實現類」。

這種作法跟以前作法的區別:

l  以前的作法中行爲的實現來自超類的具體實現,或者繼承某個接口的子類實現,這些作法都依賴於實現。

l  新的設計中,鴨子子類經過使用接口表示行爲,因此實現不會被綁定在子類中,特定的行爲編寫在實現了接口的類中。

「針對接口編程」真正的意思是「針對超類型」編程

變量的聲明類型應該是超類型,一般是一個抽象類或者接口,所以只要是實現了抽象類或者接口的對象,均可以指定給這個變量,即聲明類型時不用理會之後執行時真正的對象類型。

接下來咱們就能夠實現鴨子具體的行爲了,以下:

/// 會飛的行爲類
    /// </summary>
    public classFlyWithWings:IFlyable
    {
        public void Fly ()
        {
            Console.WriteLine("用翅膀飛");
        }
    }
 
    /// <summary>
    /// 不會飛的行爲類
    /// </summary>
    public class FlyNoWay : IFlyable
    {
        public void Fly()
        {
            Console.WriteLine("不會飛");
        }
    }
 
    /// <summary>
    /// 呱呱叫的行爲類
    /// </summary>
    public class Quack : IQuackable
    {
        public void Quack()
        {
            Console.WriteLine("呱呱叫");
        }
    }
 
    /// <summary>
    /// 吱吱叫的行爲類
    /// </summary>
    public class Squeak : IQuackable
    {
        public void Quack()
        {
            Console.WriteLine("吱吱叫");
        }
    }
 
    /// <summary>
    /// 不會叫的行爲類
    /// </summary>
    public class MuteQuack :IQuackable
    {
        public void Quack()
        {
            Console.WriteLine("不會叫");
        }
    }

接下來咱們要對鴨子的行爲進行整合,其核心思想就是:將鴨子飛行和叫的行爲委託別人處理,不使用定義在Duck類內的叫和飛行方法。

具體作法以下:

1.    在Duck類中新增2個實例變量,分別爲「flyBehavior」和「quackBehavior」聲明爲接口類型(每一個鴨子對象會動態的設置這些變量,在運行時引用正確的行爲類型)

2.    將Duck類中的Quack()和Fly()方法刪除,同時新增PerformFly()和PerformQuack()來取代這兩個類

3.    實現PerformFly()和PerformQuack(),以下所示:

/// Description:鴨子超類
    /// </summary>
    public abstract class Duck
    {
        public IFlyable flyBehavior;
        public IQuackablequackBehavior;
 
        public void PerformFly()
        {
            flyBehavior.Fly();
        }
 
        public void PerformQuack()
        {
            quackBehavior.Quack();
        }
 
        public void Swim()
        {
            Console.WriteLine("鴨子游泳");
        }
 
       
        public abstract voidDisplay();
    }

這樣作的結果就是咱們能夠忽略flyBehavior和quackBehavior接口對象究竟是什麼,只須要關心該對象如何進行相應的行爲便可。

4.    設定flyBehavior類和quackBehavior類的實例變量,以下所示:

 public class MallardDuck : Duck
    {
        public MallardDuck()
        {
            flyBehavior = newFlyWithWings();//使用FlyWithWings類處理飛行,當PerformFly()被調用時,飛的職責被委託給FlyWithWings對象,獲得真正的飛
            quackBehavior = newQuack();
        }
 
        public override voidDisplay()
        {
            Console.WriteLine("綠頭鴨");
        }
    }

說明:

當MallardDuck實例化的時候,構造器會把繼承自quackBehavior的實例變量初始化成Quack類型的新實例,一樣對於飛的行爲也是如此。

5.    測試,以下:

  Duck.Duck mallard = new MallardDuck();

            mallard.PerformFly();

            mallard.PerformQuack();

            Console.Read();

結果以下:

動態的設定行爲

爲了可以充分的用到咱們以前建的一些鴨子的動態行爲,咱們能夠在鴨子子類中經過設定方法來設定鴨子的行爲,而不是在樣子的構造器內實例化。具體步驟以下:

1.    在Duck類中新增兩個設置方法,以下:

public void SetFlyBehavior(IFlyable fly)
        {
            flyBehavior = fly;
        }
 
        public voidSetQuackBehavior(IQuackable quack)
        {
            quackBehavior = quack;
        }

2.    建立新的鴨子類型

 public class ModelDuck : Duck
    {
        public ModelDuck()
        {
            flyBehavior = newFlyNoWay();
            quackBehavior = newSqueak();
        }
        public override void Display()
        {
            Console.WriteLine("模型鴨");
        }
    }

3.    建立一個新的FlyBehavior類型

public class FlyRocket : IFlyable
    {
        public void Fly()
        {
           Console.WriteLine("Fly with Rocket!");
        }
    }

4.    測試

Duck.Duck model = new ModelDuck();
            model.PerformFly();
            model.PerformQuack();
            model.SetFlyBehavior(newFlyRocket());
            model.PerformFly();

結果以下:

從上邊的結果中咱們能夠看出,在初始化ModelDuck的時候,咱們對flyBehavior對象進行了FlyNoWay的初始化,因此顯示的飛行的行爲爲:不會飛,後邊咱們又經過SetFlyBehavior方法動態設置了flyBehavior的實例,因此後邊就有了Fly with Rocket的行爲啦。

上邊的例子對於每個鴨子都有一個FlyBehavior和QuackBehavior,當你將兩個類結合起來使用,就是組合,這種作法和繼承的不一樣之處在於,鴨子的行爲不是繼承來的,而是合適的對象組合來的。這裏用到了面向對象的第三個設計原則:

多用組合,少用繼承

經過剛剛講的這麼多,引出了本章的第一個模式:

策略模式:定義了算法簇(這裏的算法簇就至關於一組行爲),分別封裝起來,讓他們之間能夠相互替換,此模式讓算法的變化獨立於使用算法的客戶。

3.   總結

經過本章的學習,咱們能夠了解掌握如下知識:

1.    面向對象設計的原則(部分):

l 封裝變化

l 多用組合,少用繼承

l 針對接口編程,不針對實現編程

2.    面向對象模式---策略模式:

定義算法簇,分別封裝起來,讓它們之間能夠相互替換,此模式讓算法變化獨立於使用算法的客戶。

相關文章
相關標籤/搜索