亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

裝飾者模式

每天我們出門前,一定都會選擇今天上衣穿什么,褲子穿什么,搭配什么鞋子,大衣穿什么。最后一定是做好選擇,打扮好才會出門。這個過程其實就是裝飾者模要做的事情 ---- 對一個對象增加額外的功能。

我們再看一個例子。我們都吃過煎餅,除了面餅之外,我們還要加雞蛋、加蔥花、香菜、面醬、辣醬。現在還有新花樣,加辣條、加雞柳。一切都始于一張面餅,攤煎餅的過程就是在不斷對這張面餅添加新特性。
圖片描述

我們通過繼承也可以為對象增加功能,比如我們有個煎餅的父類,默認已經有面餅、面醬、雞蛋啊。那么我們可以派生出 全都放的普通煎餅、不辣的普通煎餅、不辣不放香菜的普通煎餅、不辣不放蔥的普通煎餅、全都放的辣條煎餅、全都放的雞柳煎餅…… 這只是很小一部分。通過繼承的話,由于情況太多,會造成對象爆炸。

那我們還可以通過組合的方式來擴展類啊,比如煎餅對象中,我們可以設置不同屬性,比如是否有蔥、是否有香菜、是否有辣條、是否有雞柳等等。這樣看起來也能很好的解決攤煎餅的問題。但如果想要加腸、加油條怎么辦?想要加兩個雞蛋怎么辦?我們只能修改煎餅對象。這就違反了開閉原則。顯然這樣也是不夠靈活的。

裝飾者模式能夠很好的解決對象的動態擴展,不管你想穿什么,都可以隨便搭配。不過這個煎餅要怎么做,也都能隨意的擴展支持,而不需要改已有的代碼。接下來我們就來看看如何通過裝飾者模式來攤煎餅的。

1. 實現裝飾者模式

對于攤煎餅來說,我們都是對于一個基礎的煎餅對象做裝飾,比如我想要一套兩個雞蛋、有辣椒、蔥、辣條的煎餅,那么我只需要先聲明一個基本的煎餅對象,然后用加雞蛋裝飾類裝飾它,然后再用加辣醬裝飾類裝飾它,再用加蔥的裝飾類裝飾它,最后再用加辣條的裝飾類裝飾它。最終就得到了我想要的煎餅。不過請注意,不管你怎么裝飾,最終得到的還是煎餅,并不是其他東西。

裝飾者模式的核心思想是對已有的對象,一層一層的用裝飾類去裝飾它,擴展它的特性。這樣做可以更為動態的為對象增加功能。我們看看代碼如何實現:

先定義煎餅接口:

public interface Pancake {
    void cook();
}

接口里只定義了一個制作方法。

煎餅接口的實現類:

public class BasicPancake implements Pancake {
    @Override
    public void cook() {
        System.out.println("加一勺面");
        System.out.println("加一個雞蛋");
    }
}

作為一個最基本的煎餅,總得有面,有雞蛋吧。其他的材料留給裝飾類來實現。

接下來我們定義裝飾抽象類:

public abstract class PancakeDecorator implements Pancake {
    protected Pancake pancake;

    public void setPancake(Pancake pancake) {
        this.pancake = pancake;
    }

    public void cook() {
        if (pancake != null) {
            pancake.cook();
        }
    }
}

可以看到 PancakeDecorator 同樣要實現 Pancke 接口。并且持有 Pancke 類型的引用,自己實現的 cook 方法實際調用了持有的 Pancake 對象的 cook 方法。

加辣醬的裝飾類代碼如下,其他裝飾實現類是類似的。

public class AddSpicyDecorator extends PancakeDecorator{
    @Override
    public void cook(){
        super.cook();
        System.out.println("加辣醬");
    }
}

cook 方法首先調父類的 cook 方法,然后再加入自己的特性。

客戶端代碼如下,我們看看如何利用裝飾類來生成你想要的煎餅。

public class Client {
    public static void main(String[] args) {
        Pancake pancake = new BasicPancake();
        PancakeDecorator addEggPancake = new AddEggDecorator();
        addEggPancake.setPancake(pancake);

        PancakeDecorator addSaucePancake = new AddSauceDecorator();
        addSaucePancake.setPancake(addEggPancake);

        PancakeDecorator addLaTiaoPancake = new AddLaTiaoDecorator();
        addLaTiaoPancake.setPancake(addSaucePancake);

        addLaTiaoPancake.cook();
    }
}

我們聲明了三個包裝類,對 BasicPancake 層層包裝,最后得到一套兩個雞蛋、加辣醬、加辣條的煎餅。運行后輸出如下:

加一勺面
加一個雞蛋
加一個雞蛋
加面醬
加辣條

如果你研發了新煎餅,要加新的輔料,比如香腸、榨菜之類,那么只需要增加裝飾類的實現即可。從而實現了開閉原則。

類圖如下:
圖片描述

2. 裝飾者模式優缺點

2.1 優點

  1. 動態的為對象添加額外職責:通過組合不同裝飾類,非常靈活的為對象增加額外的職責;
  2. 避免子類爆炸:當不同的特性組合,構成不同的子類時,必然造成子類爆炸。但通過裝飾者靈活組合,可以避免這個問;
  3. 分離核心功能和裝飾功能:核心業務保留在 Component 的子類中。而裝飾特性在 Decorator 的實現類中去實現。面對裝飾特性的變化,實現了開閉原則,只需要增加裝飾實現類;
  4. 很方便的重復添加特性:我想要一套兩個雞蛋,雙份辣條的煎餅。是不是只需要多裝飾一次就可以了?就是這么簡單。

2.2 缺點

  1. 由于不是通過繼承實現添加職責,所以被裝飾后的對象并不能通過對象本身就能了解其特性。而需要分析所有對其裝飾過的對象;
  2. 裝飾模式會造成有很多功能類似的小對象。通過組合不同的裝飾實現,來達成不同的需求。這樣對于不了解系統的人,比較難以學習。過多的裝飾類進行裝飾,也稍顯繁瑣。

3. 裝飾者模式適用場景

使用裝飾者模式,有以下幾種情況:

  1. 需要一個裝飾的載體。不能將全部特性都放在裝飾類中。換句話講得有個裝飾主體,核心特性在主體對象中實現。例如瀏覽器窗口,不管是加邊框還是滾動條,都是基于窗口的;
  2. 有多種特性可以任意搭配,對主體進行擴展。并且你想以動態、透明的方式來實;
  3. 不能以生成子類的方式擴展??赡苡袃煞N情況,一是對大量子類帶來的類爆炸有所顧慮。二是類定義被隱藏,或者不能用于生成子類。

4. 小結

裝飾者模式的優勢在于動態、透明的添加特性。要記住裝飾者裝飾完的對象還是之前的對象類型。通過分離核心特性和裝飾特性,客戶端代碼可以靈活的搭配使用包裝對象,從而得到具有想要行為的對象。不過要注意,有些時候裝飾的順序是要保證的。比如先放雞蛋,再放芝麻,芝麻就不會掉下去了。最好的做法是保證裝飾類的獨立。