java設計模式-裝飾者模式詳解

引例

需求:設現在有單品咖啡:Espresso(意大利濃咖啡)和LongBlack(美式咖啡),調料有Milk(牛奶)和sugar(糖),客戶可以點單品咖啡或單品咖啡+調料的組合,計算相應費用。要求在擴展新的咖啡種類時,具有良好的擴展性、改動維護方便。

拋磚引玉,我們先看看兩種一般解法。

一般解法

方案一、

枚舉創建每一種組合可能,Drink抽象類表示飲料,cost()方法計算價格,子類如Longblack_Milk表示美式咖啡加牛奶:

在這裡插入圖片描述

這樣設計十分不明智,會有很多類,當新增一個單品咖啡或調料時,類的數量就會倍增,出現類爆炸。

方案二、

把調料內置到Drink類,減少類數量過多:

在這裡插入圖片描述

方案二雖然不至於造成很多類,但是增加或刪除調料時,代碼維護量仍舊很大。

裝飾者模式

裝飾者模式(Decorator Pattern)是結構型模式,也稱裝飾器模式/修飾模式。它可以動態的將新功能附加到對象上,同時又不改變其結構。在對象功能擴展方面,它比繼承更有彈性。這種模式創建瞭一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供瞭額外的功能。

類圖:

在這裡插入圖片描述

  1. Component抽象類:主體,比如類似前面的Drink。
  2. ConcreteComponent類:具體的主體,比如前面的單品咖啡。
  3. Decorator類:裝飾者,比如前面的調料
  4. ConcreteDecorator類:具體的裝飾者,比如前面的牛奶。

如果ConcreteComponent具體子類很多,那麼可以再加一個中間層,提取共同部分,通過繼承實現更多不同的具體子類。

裝飾者解法

類圖:

在這裡插入圖片描述

Drink 類就是前面說的抽象類Decorator 是一個裝飾類,含有一個被裝飾的對象(Drink obj)和的cost()方法進行一個費用的疊加計算,遞歸的計算價格Milk和Suger是具體的裝飾者Coffee是被裝飾者主體LongBlack和Espresso是具體實現的被裝飾者實體

代碼:

抽象類

public abstract class Drink {//抽象類
    public String des; // 描述
    private float price = 0.0f;
    public String getDes() {
        return des;
    }
    public void setDes(String des) {
        this.des = des;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
    //計算費用的抽象方法
    public abstract float cost();
}

裝飾者

public class Decorator extends Drink {//裝飾者
    private Drink obj;
    public Decorator(Drink obj) { //組合
        this.obj = obj;
    }
    @Override
    public float cost() {
        // getPrice 自己價格
        return super.getPrice() + obj.cost();
    }
    @Override
    public String getDes() {
        // obj.getDes() 輸出被裝飾者的信息
        return des + " " + getPrice() + " && " + obj.getDes();
    }
}
public class Milk extends Decorator {//裝飾者子類
    public Milk(Drink obj) {
        super(obj);
        setDes(" 牛奶 ");
        setPrice(2.0f);
    }
}
public class Suger extends Decorator {//裝飾者子類
    public Suger(Drink obj) {
        super(obj);
        setDes(" 糖 ");
        setPrice(1.0f);
    }
}

被裝飾者

public class Coffee  extends Drink {//被裝飾者
    @Override
    public float cost() {
        return super.getPrice();
    }
}
public class Espresso extends Coffee {//被裝飾者子類
    public Espresso() {
        setDes(" 意式咖啡 ");
        setPrice(6.0f);
    }
}
public class LongBlack extends Coffee {//被裝飾者子類
    public LongBlack() {
        setDes(" 美式咖啡 ");
        setPrice(5.0f);
    }
}

客戶端測試

public class Client {
    public static void main(String[] args) {
        // 阿姨的卡佈奇諾:意式加兩份牛奶、一份糖
        // 1. 點一份Espresso
        Drink order = new Espresso();
        System.out.println("order1 費用=" + order.cost());
        System.out.println("order1 描述=" + order.getDes());
        // 2.1 order 加一份牛奶
        order = new Milk(order);
        System.out.println("order 加入一份牛奶 費用 =" + order.cost());
        System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
        // 2.2 order 再加一份牛奶
        order = new Milk(order);
        System.out.println("order 加入兩份牛奶 費用 =" + order.cost());
        System.out.println("order 加入兩份牛奶 描述 = " + order.getDes());
        // 3. order 加一份糖
        order = new Suger(order);
        System.out.println("order 兩份牛奶、一份糖 費用 =" + order.cost());
        System.out.println("order 兩份牛奶、一份糖 描述 = " + order.getDes());
        System.out.println("===========================");
        //美式咖啡加一份牛奶
        //1. 點一份LongBlack
        Drink order2 = new LongBlack();
        System.out.println("order2 費用 =" + order2.cost());
        System.out.println("order2 描述 = " + order2.getDes());
        //2. order2 加一份牛奶
        order2 = new Milk(order2);
        System.out.println("order2 加入一份牛奶 費用 =" + order2.cost());
        System.out.println("order2 加入一份牛奶 描述 = " + order2.getDes());
    }
}

運行結果:

在這裡插入圖片描述

總結:

裝飾者模式就像打包一個快遞,不斷的動態添加新的功能,可以組合出所有情況:

在這裡插入圖片描述

第一份Milk包含一份Espresso

第二份Milk包含(Milk+Espresso)

Suger包含(Milk+Milk+Espresso)

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: