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


最近在看HeadFirst設計模式一書,作為一個半路出家的程序員,感覺很多東西需要學習,學習的路程中有些東西學了當時覺得理解了,但日常工作中沒有使用到漸漸的自己就忘記了。----------------------上面就是寫者系列的博客的原因,主要是為了鞏固知識,忘記在那個博主那邊看過這么一句話,知識學了后總結了才變成自己的。

 

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

當然這只是理論的東西,說實話我現在都沒理解這個理論是啥,下面我用代碼和比較直觀的語言給大家介紹一下策略者模式,希望能有所收獲。

模擬情節:

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

此時由於需求的改變需要增加飛行的功能,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)由於也是繼承於超類也具備了飛行的功能,顯然繼承在這里並不是一個很好的解決方案。這里在超類中用到了抽象的方法,有個小技巧對於新學者來說經常搞不清抽象方法的語法,記住抽象方法是沒有身體的,也就是說他是沒有方法體的。

這時候就不能用繼承了,這時候有人提出把fly()和quack()方法提到接口IFlyable與IQuackable中,這樣可以飛行的鴨子實現接口,不可以飛行的模型鴨子就不實現接口,表面上看來是滿足了要求,但這樣的話不僅僅fly()在每個可以飛行的鴨子類中都要實現一邊造成代碼重復太多,如果要將可以飛行的鴨子的飛行行為稍微改動一下的話,那么面臨的將是災難,沒給方法你都需要改動一下。

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

通過上面的例子的一步步學習,我想我們應該知道繼承並不能很好的解決問題,因為鴨子的行為在子類中是不斷地變化,所以讓所有的子類都有這些行為是不恰當的。接口IFlyable與IQuackable中一開始看起來還挺不錯,解決了問題但是由於接口不具備實現代碼,所以實現接口無法達到代碼的復用。這意味者你無論何時要修改某個行為,都會造成你需要修改所有實現該接口的類中修改他們,這樣很容易造成錯誤。有個設計原則能很好的解決此問題,

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


這是我們介紹的第一個設計原則,在后面的學習中我還會介紹被的設計原則,我們先對着上面的例子看看那部分是改變的,都的鴨子的行為是不斷的變化的我們需要把行為單獨提出來,我們知道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設計模式》一書,該書很生動的講解了設計模式。我上面所寫的內容也是基於此書。最后感謝您的閱讀。

 


免責聲明!

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



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