結構型模式 - 裝飾模式

0x01 前言

??裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬于結構型模式,它是作為現有的類的一個包裝。

??這種模式創建了一個裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。

0x02 簡介

意圖:動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。

主要解決:一般的,我們為了擴展一個類經常使用繼承方式實現,由于繼承為類引入靜態特征,并且隨著擴展功能的增多,子類會很膨脹。

何時使用:在不想增加很多子類的情況下擴展類。

如何解決:將具體功能職責劃分,同時繼承裝飾者模式。

關鍵代碼: 1、Component 類充當抽象角色,不應該具體實現。 2、修飾類引用和繼承 Component 類,具體擴展類重寫父類方法。

應用實例: 1、孫悟空有 72 變,當他變成“廟宇”后,他的根本還是一只猴子,但是他又有了廟宇的功能。 2、不論一幅畫有沒有畫框都可以掛在墻上,但是通常都是有畫框的,并且實際上是畫框被掛在墻上。在掛在墻上之前,畫可以被蒙上玻璃,裝到框子里;這時畫、玻璃和畫框形成了一個物體。

優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。

缺點:多層裝飾比較復雜。

使用場景: 1、擴展一個類的功能。 2、動態增加功能,動態撤銷。

注意事項:可代替繼承。

0x03 設計概述

??咖啡是一種飲料,咖啡的本質是咖啡豆 + 水磨出來的??Х鹊戡F在要賣各種口味的咖啡,如果不使用裝飾模式,那么在銷售系統中,各種不一樣的咖啡都要產生一個類,如果有 4 種咖啡豆, 5 種口味,那么將要產生至少 20 個類(不包括混合口味),非常麻煩。使用了裝飾模式,只需要 11 個類即可生產任意口味咖啡(包括混合口味)。

角色組成

??抽象構件(Component)角色:給出一個抽象接口,以規范準備接收附加責任的對象。

??具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。

??裝飾角色(Decorator):持有一個構件(Component)對象的實例,并定義一個與抽象構件接口一致的接口。

??具體裝飾角色(Concrete Decorator):負責給構件對象“貼上”附加的責任。

0x04 具體實現

項目結構圖

圖片

抽象構件(Component)

??創建一個飲料接口,以規范準備接收附加責任的對象。

// decorator_pattern.IBeverage

package decorator_pattern;

public interface IBeverage {

    String getDescription(); // 定義添加物料描述

    double getPrice(); // 獲取返回金額數

}
具體構件(Concrete Component)

??創建實現接口的實體類,即是被裝飾的類型。

// decorator_pattern.concrete_component.CoffeeBean1

package decorator_pattern.concrete_component;

import decorator_pattern.IBeverage;

public class CoffeeBean1 implements IBeverage {

    private String description = "選擇第 1 種咖啡豆";

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double getPrice() {
        return 50;
    }

}
// decorator_pattern.concrete_component.CoffeeBean2

package decorator_pattern.concrete_component;

import decorator_pattern.IBeverage;

public class CoffeeBean2 implements IBeverage {

    private String description = "選了第 2 種咖啡豆";

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double getPrice() {
        return 100;
    }

}
裝飾角色(Decorator)

??拓展 Decorator 形成抽象裝飾類,創建一個構件(Component)對象的實例,并定義一個與抽象構件接口一致的接口,由此接口拓展具體的裝飾類。

// decorator_pattern.decorator.Decorator

package decorator_pattern.decorator;

import decorator_pattern.IBeverage;

public abstract class Decorator implements IBeverage {

    private String descriptor = "我只是裝飾器,不做具體的描述";

    @Override
    public String getDescription() {
        return descriptor;
    }

    @Override
    public double getPrice() {
        return 0;
    }

}
具體裝飾角色(Concrete Decorator)

??拓展 Decorator 形成具體的裝飾類,負責給構件對象“貼上”附加的責任。

// decorator_pattern.decorator.concrete_decorator.Milk

package decorator_pattern.decorator.concrete_decorator;

import decorator_pattern.IBeverage;
import decorator_pattern.decorator.Decorator;

public class Milk extends Decorator {

    private String description = "加了牛奶";
    private IBeverage beverage = null;

    public Milk(IBeverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + "\n" + description;
    }

    @Override
    public double getPrice() {
        return beverage.getPrice() + 30;
    }

}
// decorator_pattern.decorator.concrete_decorator.Mocha

package decorator_pattern.decorator.concrete_decorator;

import decorator_pattern.IBeverage;
import decorator_pattern.decorator.Decorator;

public class Mocha extends Decorator {

    private String description = "加了摩卡";
    private IBeverage beverage = null;

    public Mocha(IBeverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + "\n" + description;
    }

    @Override
    public double getPrice() {
        return beverage.getPrice() + 50;
    }

}
// decorator_pattern.decorator.concrete_decorator.Soy

package decorator_pattern.decorator.concrete_decorator;

import decorator_pattern.IBeverage;
import decorator_pattern.decorator.Decorator;

public class Soy extends Decorator {

    private String description = "加了豆漿";
    private IBeverage beverage = null;

    public Soy(IBeverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + "\n" + description;
    }

    @Override
    public double getPrice() {
        return beverage.getPrice() + 30;
    }

}
測試類

??進行測試舉例,飲料 1 :咖啡豆 1 ,加入牛奶,摩卡。飲料 2 :咖啡豆 2 , 加入豆漿。

// decorator_pattern.BeverageTest

package decorator_pattern;

import org.junit.Test;

import decorator_pattern.concrete_component.CoffeeBean1;
import decorator_pattern.concrete_component.CoffeeBean2;
import decorator_pattern.decorator.concrete_decorator.Frozen;
import decorator_pattern.decorator.concrete_decorator.Heat;
import decorator_pattern.decorator.concrete_decorator.Milk;
import decorator_pattern.decorator.concrete_decorator.Mocha;
import decorator_pattern.decorator.concrete_decorator.Soy;

public class BeverageTest {

    @Test
    public void testBeverage1() {
        IBeverage beverage = new CoffeeBean1();
        beverage = new Milk(beverage);
        beverage = new Mocha(beverage);
        Heat heatBeverage = new Heat(beverage);
        System.out.println(heatBeverage.getDescription() + "\n" + heatBeverage.getPrice() + "\n" + heatBeverage.getOperator());
    }

    @Test
    public void testBeverage2() {
        IBeverage beverage = new CoffeeBean2();
        beverage = new Soy(beverage);
        Frozen frozenBeverage = new Frozen(beverage);
        System.out.println(frozenBeverage.getDescription() + "\n" + frozenBeverage.getPrice() + "\n" + frozenBeverage.getOperator());
    }

}
測試結果

??testBeverage1

選擇第 1 種咖啡豆
加了牛奶
加了摩卡
130.0

??testBeverage2

選了第 2 種咖啡豆
加了豆漿
130.0

0x05 裝飾模式和適配器模式的關系

??裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過封裝其他對象達到設計的目的的,但是它們的形態有很大區別。

??理想的裝飾模式在對被裝飾對象進行功能增強的同時,要求具體構件角色、裝飾角色的接口與抽象構件角色的接口完全一致。而適配器模式則不然,一般而言,適配器模式并不要求對源對象的功能進行增強,但是會改變源對象的接口,以便和目標接口相符合。

??裝飾模式有透明和半透明兩種,這兩種的區別就在于裝飾角色的接口與抽象構件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式,要求具體構件角色、裝飾角色的接口與抽象構件角色的接口完全一致。相反,如果裝飾角色的接口與抽象構件角色接口不一致,也就是說裝飾角色的接口比抽象構件角色的接口寬的話,裝飾角色實際上已經成了一個適配器角色,這種裝飾模式也是可以接受的,稱為“半透明”的裝飾模式,如下圖所示。

圖片

??在適配器模式里面,適配器類的接口通常會與目標類的接口重疊,但往往并不完全相同。換言之,適配器類的接口會比被裝飾的目標類接口寬。

??顯然,半透明的裝飾模式實際上就是處于適配器模式與裝飾模式之間的灰色地帶。如果將裝飾模式與適配器模式合并成為一個“包裝模式”的話,那么半透明的裝飾模式倒可以成為這種合并后的“包裝模式”的代表。

0x06 透明性要求

??裝飾模式對客戶端的透明性要求程序不要聲明一個 Concrete Component 類型的變量,而應當聲明一個 Component 類型的變量。

??用上面的例子來說,必須永遠把所有的飲料當成飲料來對待,而如果把飲料變成的加摩卡的飲料當成摩卡,而不是飲料,這是不應當發生的。

??下面的做法是對的:

IBeverage beverage = new CoffeeBean1();
IBeverage mochaBeverage = new Mocha(beverage);

??下面的做法是錯的:

IBeverage beverage = new CoffeeBean1();
Mocha mochaBeverage = new Mocha(beverage);

0x07 半透明模式

??然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強所考慮的類的性能。在增強性能的時候,往往需要建立新的公開的方法。上述例子中,我們需要對咖啡進行加熱和冰凍,就需要在原先的接口上添加新的方法。因此,在 Concrete Decorator 類模塊中,添加新的具體裝飾類 Heat ,在類 Heat 中增加方法 getOperator() 對咖啡進行進一步操作。

項目結構圖

圖片

具體實現

具體裝飾角色(Concrete Decorator)

??拓展 Decorator 形成具體的裝飾類 Heat,對咖啡進行加熱。

// decorator_pattern.decorator.concrete_decorator.Heat

package decorator_pattern.decorator.concrete_decorator;

import decorator_pattern.IBeverage;
import decorator_pattern.decorator.Decorator;

public class Heat extends Decorator {

    private IBeverage beverage = null;

    public Heat(IBeverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription();
    }

    @Override
    public double getPrice() {
        return beverage.getPrice();
    }

    public String getOperator() {
        return "加熱";
    }

}

??拓展 Decorator 形成具體的裝飾類 Frozen,對咖啡進行冰凍。

// decorator_pattern.decorator.concrete_decorator.Frozen

package decorator_pattern.decorator.concrete_decorator;

import decorator_pattern.IBeverage;
import decorator_pattern.decorator.Decorator;

public class Frozen extends Decorator {

    private IBeverage beverage = null;

    public Frozen(IBeverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription();
    }

    @Override
    public double getPrice() {
        return beverage.getPrice();
    }

    public String getOperator() {
        return "冰凍";
    }

}

??這就導致了大多數的裝飾模式的實現都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。這意味著客戶端可以聲明 Concrete Decorator 類型的變量,從而可以調用 Concrete Decorator 類中才有的方法:

測試類
package decorator_pattern;

import org.junit.Test;

import decorator_pattern.concrete_component.CoffeeBean1;
import decorator_pattern.concrete_component.CoffeeBean2;
import decorator_pattern.decorator.concrete_decorator.Frozen;
import decorator_pattern.decorator.concrete_decorator.Heat;
import decorator_pattern.decorator.concrete_decorator.Milk;
import decorator_pattern.decorator.concrete_decorator.Mocha;
import decorator_pattern.decorator.concrete_decorator.Soy;

public class BeverageTest {

    @Test
    public void testBeverage1() {
        IBeverage beverage = new CoffeeBean1();
        beverage = new Milk(beverage);
        beverage = new Mocha(beverage);
        Heat heatBeverage = new Heat(beverage);
        System.out.println(heatBeverage.getDescription() + "\n" + heatBeverage.getPrice() + "\n" + heatBeverage.getOperator());
    }

    @Test
    public void testBeverage2() {
        IBeverage beverage = new CoffeeBean2();
        beverage = new Soy(beverage);
        Frozen frozenBeverage = new Frozen(beverage);
        System.out.println(frozenBeverage.getDescription() + "\n" + frozenBeverage.getPrice() + "\n" + frozenBeverage.getOperator());
    }

}

??半透明的裝飾模式是介于裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口,也可以通過改寫一個或幾個方法,或增加新的方法來增強或改變所考慮的類的功能。大多數的裝飾模式實際上是半透明的裝飾模式,這樣的裝飾模式也稱做半裝飾、半適配器模式。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容