使用裝飾器模式解決煎餅「加碼」問題

2020-10-13 01:00:22
在《裝飾設計模式》一節中,我們提到了煎餅,煎餅中可以加雞蛋,也可以加香腸,但是不管怎麼“加碼”,都還是一個煎餅。

下面用程式碼來模擬給煎餅“加碼”的業務場景。為了能讓大家更好的理解裝飾器模式帶來的好處,先來看不用裝飾器模式的情況。

首先建立一個煎餅 Pancake 類。
public class Pancake {
    protected String getMsg() {
        return "煎餅";
    }

    public int getPrice() {
        return 5;
    }
}
然後建立一個加雞蛋的煎餅 PancakeWithEgg 類。
public class PancakeWithEgg extends Pancake {
    @Override
    protected String getMsg() {
        return super.getMsg() + "一個雞蛋";
    }

    @Override
    //加1個雞蛋加1元錢
    protected int getPrice() {
        return super.getPrice() + 1;
    }
}
再建立一個既加雞蛋又加香腸的 PancakeWithEggAndSausage 類。
public class PancakeWithEggAndSausage extends PancakeWithEgg {
    @Override
    protected String getMsg() {
        return super.getMsg() + "一根香腸";
    }

    @Override
    //加1根香腸加2元錢
    protected int getPrice() {
        return super.getPrice() + 2;
    }
}
最後編寫使用者端測試程式碼。
public class Test {
    public static void main(String[] args) {
        Pancake pancake = new Pancake();
        System.out.println(pancake.getMsg() + ",總價格:" + pancake.getPrice());

        Pancake pancakeWithEgg = new PancakeWithEgg();
        System.out.println(pancakeWithEgg.getMsg() + ",總價格:" + pancakeWithEgg.getPrice());

        Pancake pancakeWithEggAndSausage = new PancakeWithEggAndSausage();
        System.out.println(pancakeWithEggAndSausage.getMsg() + ",總價格:" + pancakeWithEggAndSausage.getPrice());
    }
}
執行結果如下所示:

煎餅,總價格:5
煎餅一個雞蛋,總價格:6
煎餅一個雞蛋一根香腸,總價格:8

執行結果是沒有問題的。但是,如果使用者需要一個加 2 個雞蛋 1 根香腸的煎餅,用以上類結構是建立不出來的,也無法自動計算出價格,除非再建立一個類做客製化。如果需求改變,那麼一直加客製化顯然是不科學的。

使用裝飾器模式解決煎餅加碼問題

下面用裝飾器模式來解決上面的問題,首先建立一個煎餅的抽象 Pancake 類。
public abstract class Pancake {
    protected abstract String getMsg();

    protected abstract int getPrice();
}
建立一個基本的煎餅(或者說是基本套餐)BasePancake 類。
public class BasePancake extends Pancake {
    protected String getMsg() {
        return "煎餅";
    }

    public int getPrice() {
        return 5;
    }
}
然後建立一個擴充套件套餐的抽象裝飾器 PancakeDecorator 類。
public abstract class PancakeDecorator extends Pancake {
    //靜態代理,委派
    private Pancake pancake;

    public PancakeDecorator(Pancake pancake) {
        this.pancake = pancake;
    }

    protected abstract void doSomething();

    @Override
    protected String getMsg() {
        return this.pancake.getMsg();
    }

    @Override
    public int getPrice() {
        return this.pancake.getPrice();
    }
}
建立雞蛋裝飾器 EggDecorator 類。
public class EggDecorator extends PancakeDecorator {
    public EggDecorator(Pancake pancake) {
        super(pancake);
    }

    protected void doSomething() {

    }

    @Override
    protected String getMsg() {
        return super.getMsg() + "1個雞蛋";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}
建立香腸裝飾器 SausageDecorator 類。
public class SausageDecorator extends PancakeDecorator {
    public SausageDecorator(Pancake pancake) {
        super(pancake);
    }

    protected void doSomething() {

    }

    @Override
    protected String getMsg() {
        return super.getMsg() + "1根香腸";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 2;
    }
}
使用者端測試程式碼如下。
public class Test {
    public static void main(String[] args) {
        Pancake pancake;
        //買一個煎餅
        pancake = new BasePancake();
        //加一個雞蛋
        pancake = new EggDecorator(pancake);
        //再加一個雞蛋
        pancake = new EggDecorator(pancake);
        //再加一根香腸
        pancake = new SausageDecorator(pancake);

        //與靜態代理的最大區別就是職責不同
        //靜態代理不一定要滿足 is-a 的關係
        //靜態代理會做功能增強,同一個職責變得不一樣

        //裝飾器更多考慮的是擴充套件
        System.out.println(pancake.getMsg() + ",總價:" + pancake.getPrice());
    }
}
執行結果如下所示。

煎餅1個雞蛋1個雞蛋1根香腸,總價:9

最後來看類圖,如下圖所示。
煎餅”加碼“UML類圖