天天看點

設計模式之政策模式

  今天我們來看一下政策模式。在真正介紹政策模式之前呢,我們先通過一個非常生動有趣的例子的引入。

  我們現在有一個模拟鴨子的程式。

  1、鴨子父類如下:

public abstract class Duck {

    /**
     * 呱呱叫行為(所有鴨子都會)
     */
    public void quack() {
        System.out.println("呱呱叫");
    }

    /**
     * 遊泳行為(所有鴨子都會)
     */
    public void swim() {
        System.out.println("遊泳");
    }

    /**
     * 每個鴨子額外觀都不同  是以display是抽象方法
     */
    abstract void display();
}      

  MallardDuck子類

public class MallardDuck extends Duck {

    /**
     *頭是綠色的
     */
    @Override
    void display() {
        System.out.println("外觀是綠頭的鴨子");
    }
}      

  RedheadDuck子類

public class RedheadDuck extends Duck {

    /**
     * 外觀是紅頭
     */
    @Override
    void display() {
        System.out.println("外觀是紅頭的鴨子");
    }
}      

  2、現在呢,有一個需求,需要讓鴨子具備會飛的功能。是以,理所當然的就給Duck父類添加上了一個fly()方法;但是這裡問題出現了,一些不具備會飛行為的鴨子也具備了“飛”的行為(比如,我們現在有一個橡皮鴨子,它不會飛,并且不會呱呱叫,隻會吱吱叫)。

  3、這時候,我們想到,可以在重寫橡皮鴨子的fiy()方法,讓它什麼都不做,并重寫quack()方法,讓它吱吱叫。代碼如下:

public class RubberDuck extends Duck {

    /**
     * 不會飛 什麼都不做
     */
    @Override
    public void display() {
        System.out.println("");
    }

    /**
     * 吱吱叫(不會 呱呱叫)
     */
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}      

  4、可是,如果以後我們加入誘餌鴨(DecoyDuck),它是一隻假鴨子,不會飛也不會叫。難道我們要接着重寫方法嗎?

  5、利用接口如何?那好,加入我們利用接口,把fly()方法從超類中取出來,放進“Fiyable”接口中。這麼一來,隻有會飛的鴨子才實作此接口,同樣的方式,也可以設計一個“Quackable”接口,因為并不是所有的鴨子都會叫。

  用這種方法确實解決了一部分的問題(不會再有會飛的橡皮鴨子),但是卻造成代碼無法被複用(因為每一個子類都需要實作接口的方法)。

  現在我們想一下,會不會有一種對既有的代碼影響最小的方式來修改程式?

  6、現在我們有一個設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。以此讓我們的代碼變化更少,系統更加有彈性。

  7、為了讓程式有彈性,并且我們還想能夠指定行為到鴨子的執行個體,讓鴨子可以在“運作時”改變“鴨子”的行為。從現在開始,鴨子的行為将被放在分開的類中,此類專門提供某行為的接口的實作。這樣,鴨子就不再需要知道行為的實作細節。(設計原則:針對接口程式設計,而不是針對實作程式設計)。

  是以,我們先聲明兩個接口,FlyBehavior和QuackBehavior,而行為的每個實作都将實作其中的一個接口。直接上代碼。

  FlyBehavior(“飛”行為接口):

public interface FlyBehavior {

    /**
     * 飛(所有的新的飛行類都必須實作fly方法)
     */
    void fly();
}      

  用翅膀飛類:

public class FlyWithWings implements FlyBehavior {

    @Override
    public void fly() {
        System.out.println("用翅膀來飛行");
    }
}      

  不會飛類:

public class FlyNoWay implements FlyBehavior {

    /**
     * 不會飛
     */
    @Override
    public void fly() {
        System.out.println("");
    }
}      

  QuackBehavior(“叫”行為接口):

public interface QuackBehavior {

    /**
     * 呱呱叫行為(每一個新的叫行為都必須實作quack方法)
     */
    void quack();
}      

  呱呱叫行為類:

public class Quack implements QuackBehavior {

    /**
     * 呱呱叫
     */
    @Override
    public void quack() {
        System.out.println("呱呱叫");
    }
}      

  吱吱叫行為類:

public class Squeak implements QuackBehavior {

    /**
     * 吱吱叫
     */
    @Override
    public void quack() {
        System.out.println("吱吱叫");
    }
}      

  不會叫類:

public class MuteQuack implements QuackBehavior {

    /**
     * 不會叫
     */
    @Override
    public void quack() {
        System.out.println("");
    }
}      

  8、下面,我們來重新改寫鴨子父類:

public abstract class Duck {

    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    /**
     * 遊泳行為(所有鴨子都會)
     */
    public void swim() {
        System.out.println("遊泳");
    }

    /**
     * 每個鴨子額外觀都不同  是以display是抽象方法
     */
    public abstract void display();

    /**
     * 飛行為
     */
    public void performFly() {
        flyBehavior.fly();
    }

    /**
     * 呱呱叫行為
     */
    public void performQuack() {
        quackBehavior.quack();
    }

    /**
     * 設定飛行為
     * @param flyBehavior
     */
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    /**
     * 設定叫行為
     * @param quackBehavior
     */
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}      

  重寫綠頭鴨類:

public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    /**
     *頭是綠色的
     */
    @Override
    public void display() {
        System.out.println("外觀是綠頭的鴨子");
    }
}      

  通過我們的改寫,會發現,我們的綠頭鴨子在new一個對象的時候,會呱呱叫,會用翅膀飛,我們還可以運作時改寫它的飛行為的方式,可以把它設為不會飛,或者我們在寫一個FlyBehavior的實作類,用另外一種方式來飛,這些都是可以的。

 我們寫一個測試類:

public static void main(String[] args) {
        Duck mallardDuck = new MallardDuck();
        mallardDuck.performFly();
        mallardDuck.setFlyBehavior(new FlyNoWay());
        mallardDuck.performFly();
    }      

  執行結果:

設計模式之政策模式

  9、最後,我們會發現,如果再來新的需求,我們不需要改變原來的任何代碼,比如來了一隻會用火箭飛的鴨子,那我們隻需要新寫一個FlyBehavior的實作類,然後在構造器裡寫出來就行了。對修改關閉,對擴充開放。

  10、現在  我們已經學會了政策模式。哈哈。政策模式:定義了算法族,分别封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。

最後來個總結:

意圖:定義一系列的算法,把它們一個個封裝起來, 并且使它們可互相替換。

主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。

何時使用:一個系統有許多許多類,而區分它們的隻是他們直接的行為。

如何解決:将這些算法封裝成一個一個的類,任意地替換。

關鍵代碼:實作同一個接口。

應用執行個體: 1、諸葛亮的錦囊妙計,每一個錦囊就是一個政策。 2、旅行的出遊方式,選擇騎自行車、坐汽車,每一種旅行方式都是一個政策。 3、JAVA AWT 中的 LayoutManager。

優點: 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴充性良好。

缺點: 1、政策類會增多。 2、所有政策類都需要對外暴露。

使用場景: 1、如果在一個系統裡面有許多類,它們之間的差別僅在于它們的行為,那麼使用政策模式可以動态地讓一個對象在許多行為中選擇一種行為。 2、一個系統需要動态地在幾種算法中選擇一種。 3、如果一個對象有很多的行為,如果不用恰當的模式,這些行為就隻好使用多重的條件選擇語句來實作。

注意事項:如果一個系統的政策多于四個,就需要考慮使用混合模式,解決政策類膨脹的問題。

繼續閱讀