HeadFirst設計模式(一)策略者模式

最近在看HeadFirst設計模式一書,做爲一個半路出家的程序員,感受不少東西須要學習,學習的路程中有些東西學了當時以爲理解了,但平常工做中沒有使用到漸漸的本身就忘記了。----------------------上面就是寫者系列的博客的緣由,主要是爲了鞏固知識,忘記在那個博主那邊看過這麼一句話,知識學了後總結了才變成本身的。程序員

 

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

固然這只是理論的東西,說實話我如今都沒理解這個理論是啥,下面我用代碼和比較直觀的語言給你們介紹一下策略者模式,但願能有所收穫。編程

模擬情節:設計模式

    Joe上班的公司有一套鴨子游戲的系統:SimUDuck,遊戲中有各類鴨子,一邊游泳,一邊呱呱叫。此係統使用了標準的OO(面向對象)技術。設計一個鴨子的超類(也就是父類-Duck),並讓各類鴨子繼承此超類。ide

此時因爲需求的改變須要增長飛行的功能,Joe直接在超類中添加fly()方法:學習

 1   public abstract class Duck
 2     {
 3         public  void quack()
 4         {
 5             Console.WriteLine("我會叫");
 6         }
 7 
 8         public void swim()
 9         {
10             Console.WriteLine("我會游泳");
11         }
12 
13         public void fly()
14         {
15             Console.WriteLine("我會飛");
16         }
17         public abstract void display();
18     }
19 
20     public class MallardDuck : Duck
21     {
22         public override  void  display()
23         {
24             Console.WriteLine("我是一隻綠頭鴨");
25         }
26 
27     }
28     public class ModelDuck : Duck
29     {
30         public override void display()
31         {
32             Console.WriteLine("我是一隻模型鴨子");
33         }
34     }
35     public class RedheadDuck : Duck
36     {
37 
38         public override  void display()
39         {
40             Console.WriteLine("我是一隻紅頭鴨");
41         }
42     }
43 
44     class Program
45     {
46         static void Main(string[] args)
47         {
48             Duck  modelDuck=new ModelDuck();
49             modelDuck.quack();
50             modelDuck.display();
51             modelDuck.fly();
52             Console.ReadLine();
53         }
54     }

此時問題就來了,模型鴨子(ModelDuck)因爲也是繼承於超類也具有了飛行的功能,顯然繼承在這裏並非一個很好的解決方案。這裏在超類中用到了抽象的方法,有個小技巧對於新學者來講常常搞不清抽象方法的語法,記住抽象方法是沒有身體的,也就是說他是沒有方法體的。spa

這時候就不能用繼承了,這時候有人提出把fly()和quack()方法提到接口IFlyable與IQuackable中,這樣能夠飛行的鴨子實現接口,不能夠飛行的模型鴨子就不實現接口,表面上看來是知足了要求,但這樣的話不只僅fly()在每一個能夠飛行的鴨子類中都要實現一邊形成代碼重複太多,若是要將能夠飛行的鴨子的飛行行爲稍微改動一下的話,那麼面臨的將是災難,沒給方法你都須要改動一下。設計

此時,你是否指望一種設計模式能來救你於苦海,這裏我先賣一個關子,接下來我會用老方法找出一個解決之道,「採用良好的OO軟件設計原則」;code

經過上面的例子的一步步學習,我想咱們應該知道繼承並不能很好的解決問題,由於鴨子的行爲在子類中是不斷地變化,因此讓全部的子類都有這些行爲是不恰當的。接口IFlyable與IQuackable中一開始看起來還挺不錯,解決了問題可是因爲接口不具有實現代碼,因此實現接口沒法達到代碼的複用。這意味者你不管什麼時候要修改某個行爲,都會形成你須要修改全部實現該接口的類中修改他們,這樣很容易形成錯誤。有個設計原則能很好的解決此問題,orm

設計原則:找出應用中可能須要變化的部分,把他們獨立出來,不要和那些不須要變化的代碼混合在一塊兒。


這是咱們介紹的第一個設計原則,在後面的學習中我還會介紹被的設計原則,咱們先對着上面的例子看看那部分是改變的,都的鴨子的行爲是不斷的變化的咱們須要把行爲單獨提出來,咱們知道Duck類內的fly()和quack()會隨着鴨子的改變而不斷的改變,爲了把這兩個行爲從Duck類中分開,咱們將把他們從Duck類中取出來,創建一組新類來表明每一個行爲。

設計鴨子的行爲

如何設計那組實現飛行和呱呱叫的行爲呢?

首先咱們但願一切具備彈性,一開始的鴨子系統正是由於沒有彈性,才讓咱們走上這條路,咱們還行可以‘指定’行爲到鴨子的實例。咱們想要產生一個新的綠鴨子的實例的時候,並指定特定‘類型’的飛行行爲給它,乾脆順便讓鴨子的行爲能夠動態的改變好了。換句話說,咱們應該在鴨子類中包含設定行爲的方法,這樣就能夠在‘運行時’動態地‘改變’綠頭鴨子的行爲。

有了這些目標的需求,接着咱們來看咱們的第二給設計原則

設計原則:針對接口編程,而不是針對實現編程

從如今開始,鴨子的行爲將被放在分開的類中,此類專門提供某行爲接口的實現。這樣的話和以前的作法有了明顯的不一樣,以前的作法是:行爲來自Duck超類的具體實現,或者繼承某給接口並由子類自行實現行爲。這兩種作法都是依賴於‘實現’形成咱們的系統再也不有彈性,很難更改別的代碼。

在咱們新的實現中,鴨子的子類將使用接口(IFlyBehavior與IQuackBehavior)所表示的行爲,也就是實現此接口的行爲類(FlyNoWay等)具體的行爲的實現如今編寫在行爲類中。這樣的設計就使得咱們的系統具有的彈性。

這樣的設計,可讓飛行和呱呱叫的動做被其餘的對象複用,由於這些行爲已經與鴨子類無關了。而咱們也能夠新增一些行爲,不會影響到既有的行爲類,也不會影響‘使用’到飛行行爲的鴨子類。

整合鴨子的行爲

關鍵在於,鴨子如今會將飛行和呱呱叫的動做‘委託’(delegate)別人處理,而不是使用定義在Duck類(或子類)內的呱呱叫和飛行方法。

作法的這樣的:

①首先,在Duck類中‘加入兩個實例變量’,分別爲」iFlyBehavior「與」IQuackBehavior「,聲明爲接口類型(而不是具體實現接口的類的類型)每一個鴨子對象經過動態的實現這些變量以在運行時引用正確的行爲類型。

咱們用兩個類似的方法performFly和performQuack來取代Duck類中fly()與quack()。稍後你就知道緣由了。

②如今,咱們來實現

 

performQuack();public class Duck
{ IQuackBehavior iQuackBehavior;//每隻鴨子都會引用實現IQuackbehavior接口的對象。 public void performQuack() { iQuackBehavior.quack();//鴨子對象不親自處理呱呱叫行爲,而是委託給iQuackBehavior引用的對象。

   }
}

 

Duck對象只要叫iQuackBehavior對象去呱呱叫就能夠了,在這部分的代碼中,咱們不在關心iQuackBehavior接口的對象究竟是什麼,咱們只關心該對象知道如何進行呱呱叫就夠了。

3.如今關心如何實現iQuackBehavior與iFlyBehavior的實例變量,看看具體繼承Duck類的子類是怎麼實現的吧

    public class ModelDuck : Duck
    {
        public ModelDuck()
        {
            iFlyBehaviro = new DuckNoFly();
            iQueackBehaviro = new DuckNoQueck();
        }           
        public override void Display()
        {
            Console.WriteLine("我是一隻木頭鴨子");
        }
    }

 在ModelDuck實例化時,它的構造器會把繼承來的iFlyBehavior和iQuackBehavior實例變量初始化成DuckNoFly與DuckNoQuack類型的新實例。(DuckNoFly與DuckNoQuack是接口的具體實現)

如今只剩下一個在運行時動態該百年鴨子的行爲的功能還未實現,咱們上面是在具體鴨子類的構造器內進行更改鴨子的行爲的,若是換一個地方將更改鴨子的行爲提取到Duck超類中,那麼只要繼承自該超類的鴨子派生類不都具有了動態更改行爲的能力了嗎?

在Duck類中,加入兩個新方法:

    public void setPerformFly(IFlyBehaviro fb)
        {
            iFlyBehaviro = fb;
        }
        public void setPerformQuack(IQuackBehaviro qb)
        {
            iQueackBehaviro = qb;
        }

  今後之後,咱們能夠‘隨時’調用者兩個方法改變鴨子的行爲。下面我貼上更改後的所有代碼:

 public abstract class Duck
    {
        public IFlyBehaviro iFlyBehaviro;
        public IQuackBehaviro iQueackBehaviro;
        public abstract void Display();

        public void Swim()
        {

        }
        public void performFly()
        {
            iFlyBehaviro.fly();
        }
        public void performQuack()
        {
            iQueackBehaviro.quack();
        }
        public void setPerformFly(IFlyBehaviro fb)
        {
            iFlyBehaviro = fb;
        }
        public void setPerformQuack(IQuackBehaviro qb)
        {
            iQueackBehaviro = qb;
        }
    }
    public class ModelDuck : Duck
    {
        public ModelDuck()
        {
            iFlyBehaviro = new DuckNoFly();
            iQueackBehaviro = new DuckNoQueck();
        }           
        public override void Display()
        {
            Console.WriteLine("我是一隻木頭鴨子");
        }
    }

   public interface IFlyBehaviro
    {
         void fly();
    }
    public interface IQuackBehaviro
    {
        void quack();
    }
    public class DuckNoFly : IFlyBehaviro
    {
        public void fly()
        {
            Console.WriteLine("我不會飛");
        }
    }
    public class DuckCanFlay:IFlyBehaviro
    {
        public void fly()
        {
            Console.WriteLine("我會飛");
        }
    }
    public class DuckNoQueck : IQuackBehaviro
    {
        public void quack()
        {
            Console.WriteLine("我不會叫");
        }
    }
    public class DuckGuaGuaQueck:IQuackBehaviro
    {
        public void quack()
        {
            Console.WriteLine("呱呱叫");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
           
            Duck modelDuck = new ModelDuck();
            modelDuck.Display();
            modelDuck.performFly();
            modelDuck.performQuack();

            modelDuck.setPerformFly(new DuckCanFlay());
            modelDuck.performFly();
            Console.ReadLine();
        }
    }

  封裝行爲的大局觀

咱們已經深刻研究了鴨子模擬器的設計,該是看看總體的格局了:

這是從新設計後的類結構,你所指望的一切都有:鴨子繼承Duck,飛行行爲實現IFlyBehavior接口,呱呱叫行爲實現IQuackBehavior接口。請特別注意類之間的」關係「,關係能夠是IS-A(是一個)、HAS-A(有一個)或IMPLEMENTS(實現)。

」有一個「可能比」是一個「更好

」有一個「關係至關有趣:每個鴨子都有一個IFlyBehavior和一個IQuackBehavior,好將飛行和呱呱叫的委託給它們代爲處理。

當你將兩個類結合起來使用,如同本例通常,這就是組合。這種作法和」繼承「不一樣的地方在於,鴨子的行爲不是經過繼承來的,而是和適當的行爲對象」組合「來的。

這是一個很重要的技巧,其實也是咱們介紹的第三給設計原則:

設計原則:多用組合,少用繼承。

如你所見,使用組合創建系統具備很大的彈性,不只能夠將算法組封裝成類,更能夠」在運行時動態的改變行爲「,只要組合的行爲對象符合正確的接口標準便可。」組合「被普遍的應用在不少設計模式中,後面你會常常發現它的身影。

至此,這就是咱們學習的第一個模式了。

在上面的內容中咱們須要到了幾項內容:

OO基礎:抽象,封裝,多態,繼承。

OO原則:封裝變化;多用組合,少用繼承;針對接口編程,不針對實現編程。

OO模式:策略模式----定義算法族,分別封裝起來,讓他們直接能夠互相替換,此模式讓算法的變化獨立於使用算法的客戶。

初衷,只是想把學習的設計模式本身總結成博客記錄下來,可是好像有點偏離初心更像是一個教別人怎麼理解策略者模式了,水平有限,寫的不是很好,對本內容感興趣的讀者,推薦你閱讀《Head First設計模式》一書,該書很生動的講解了設計模式。我上面所寫的內容也是基於此書。最後感謝您的閱讀。

相關文章
相關標籤/搜索