裝飾者模式,從吃黃燜雞開始提及

黃燜雞米飯最熱賣的外賣之一,國人都喜歡吃,吃過黃燜雞米飯的應該都知道,除了黃燜雞米飯主體外,還能夠添加各類配菜,如土豆、香菇、鵪鶉蛋、青菜等。若是須要你來設計一套黃燜雞米飯結帳系統,你該如何設計呢?java

前置條件:主體:黃燜雞米飯 價格:16,配菜:土豆 價格:二、香菇 價格:二、鵪鶉蛋 價格:二、青菜 價格:1.5git

這還不簡單?看個人,你隨手就來了下面這段代碼。github

public class HuangMenJiMiFan {
    // 黃燜雞價格
    private double huangMenJiPrice = 16D;
    // 土豆價格
    private double potatoPrice = 2D;
    // 鵪鶉蛋價格
    private double eggPrice = 2D;
    // 香菇價格
    private double mushroomPrice = 2D;
    // 青菜價格
    private double vegPrice = 1.5D;
    // 總價格
    private double totalPrice = 0D;
    // 訂單描述
    private StringBuilder desc = new StringBuilder("黃燜雞米飯 ");

    // 是否加土豆
    private boolean hasPotato = false;
    // 是否加鵪鶉蛋
    private boolean hasEgg = false;
    // 是否加香菇
    private boolean hasMushroom = false;
    // 是否加蔬菜
    private boolean hasVeg = false;

    public HuangMenJiMiFan(){
        this.totalPrice = this.huangMenJiPrice;
    }

    public void setHasPotato(boolean hasPotato) {
        this.hasPotato = hasPotato;
    }

    public void setHasEgg(boolean hasEgg) {
        this.hasEgg = hasEgg;
    }

    public void setHasMushroom(boolean hasMushroom) {
        this.hasMushroom = hasMushroom;
    }

    public void setHasVeg(boolean hasVeg) {
        this.hasVeg = hasVeg;
    }

    public String getDesc(){
        if (hasEgg){
            this.desc.append("+ 一份鵪鶉蛋 ");
        }
        if (hasMushroom){
            this.desc.append("+ 一份香菇 ");
        }
        if (hasPotato){
            this.desc.append("+ 一份土豆 ");
        }
        if (hasVeg){
            this.desc.append("+ 一份蔬菜 ");
        }
        return desc.toString();
    }

    public double cost(){
        if (hasEgg){
            this.totalPrice +=this.eggPrice;
        }
        if (hasMushroom){
            this.totalPrice +=this.mushroomPrice;
        }
        if (hasPotato){
            this.totalPrice +=this.potatoPrice;
        }
        if (hasVeg){
            this.totalPrice +=this.vegPrice;
        }
        return totalPrice;
    }
}
複製代碼

只要在點黃燜雞米飯的時候,把添加的配菜設置成true就好,這段代碼確實解決了黃燜雞米飯結算問題。可是我須要加兩份土豆呢?我須要添加一種新配菜呢?或者我新增一個黃燜排骨呢?這時候實現起來就須要去改動原來的代碼,這違背了設計模式的開放-關閉原則設計模式

開放-關閉原則:類應該對擴展開放,對修改關閉微信

上面的設計違背了開放-關閉原則,爲了不這個問題,採用裝飾者模式彷佛是一種可行的解決辦法。app

裝飾者模式:動態的給一個對象添加一些額外的職責,就增長功能來講,裝飾模式比生成子類更爲靈活。ide

裝飾者模式的通用類圖以下: 學習

裝飾者模式的通用類圖
從類圖中,咱們能夠看出裝飾者模式有四種角色:

  • Component:核心抽象類,裝飾者和被裝飾者都須要繼承這個抽象類
  • ConcreteComponent:對裝飾的對象,該類必須繼承Component
  • Decorator:裝飾者抽象類,抽象出具體裝飾者須要裝飾的接口
  • ConcreteDecorator:具體的裝飾者,該類必須繼承Decorator類,而且裏面有一個變量指向Component抽象類

裝飾者模式的核心概念咱們都知道了,那就來實現一把,用裝飾者模式來設計黃燜雞米飯的結帳系統。測試

Component類的設計,仔細想一想,無論黃燜雞米飯仍是配菜都會涉及到金額計算。因此咱們把該方法抽象到Component類。來設計咱們黃燜雞米飯結帳系統的Component類,咱們取名叫作Food,Food類的具體設計以下:ui

/** * 核心抽象類 */
public abstract class Food {

    String desc = "食物描述";

    public String getDesc() {
        return this.desc;
    }
    // 價格計算
    public abstract double cost();
}
複製代碼

ConcreteComponent類是咱們具體的被裝飾對象,咱們這裏的裝飾對象是黃燜雞米飯,咱們來設計咱們黃燜雞米飯的被裝飾對象Rice類,Rice類的具體實現以下:

/** * 被裝飾者-黃燜雞米飯 */
public class Rice extends Food{
    public Rice(){
        this.desc ="黃燜雞米飯";
    }
    @Override
    public double cost() {
        // 黃燜雞米飯的價格
        return 16D;
    }
}
複製代碼

Decorator類是裝飾者的抽象類,咱們須要定義一個getDesc()的抽象接口,由於在Food類中,getDesc()不是抽象的,在後面的具體裝飾者中,須要重寫getDesc()類,因此咱們須要將抽象在裝飾者這一層。咱們來設計黃燜雞米飯結帳系統的裝飾者抽象類FoodDecoratorFoodDecorator類的具體設計以下:

public abstract class FoodDecorator extends Food {
    // 獲取描述
    public abstract String getDesc();
}
複製代碼

ConcreteDecorator類是具體的裝飾者,咱們有四個具體的裝飾者,分別是土豆、香菇、鵪鶉蛋、青菜,具體的裝飾者須要作的事情是計算出被裝飾者裝飾完裝飾品後的總價格和更新商品的描述。四個具體裝飾者的設計以下:

public class Egg extends FoodDecorator {
    String desc = "雞蛋";
    // 存放Component對象,該對象多是被裝飾後的
    Food food;

    public Egg(Food food){
        this.food = food;
    }

    // 計算總價 當前Component對象的價格加上當前裝飾者的價格
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}

複製代碼
public class Mushroom extends FoodDecorator {
    String desc = "香菇";
    Food food;

    public Mushroom(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
複製代碼
public class Potato extends FoodDecorator {
    String desc = "土豆";
    Food food;

    public Potato(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 2D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
複製代碼
public class Veg extends FoodDecorator {
    String desc = "蔬菜";
    Food food;
    public Veg(Food food){
        this.food = food;
    }
    // 計算總價
    @Override
    public double cost() {
        return food.cost() + 1.5D;
    }
    @Override
    public String getDesc() {
        return food.getDesc()+" + "+this.desc;
    }
}
複製代碼

裝飾者的全部角色都實現完了,咱們來測試一下使用裝飾者模式以後的黃燜雞結帳系統,編寫一個App測試類。

public class App {
    public static void main(String[] args) {
        // 點一份米飯
        Rice rice = new Rice();
        // 加個雞蛋
        Egg egg = new Egg(rice);
        // 在加土豆
        Potato potato = new Potato(egg);
        // 再加一份白菜
        Veg veg = new Veg(potato);
        System.out.println(veg.getDesc());
        System.out.println(veg.cost());
    }
}
複製代碼

測試結果

咱們的描述和金額都是正確的,可能你仍是沒怎麼明白裝飾者模式,一塊兒來看看咱們的黃燜雞米飯被裝飾後的示意圖:

咱們的黃燜雞米飯共有三層裝飾,第一層是雞蛋,第二層是土豆,第三層是蔬菜。咱們在最後調用價格計算和商品描述都是調用了最外層的裝飾者的方法,有點像遞歸同樣,每一層的裝飾者都有被前一個裝飾者裝飾後的黃燜雞米飯對象。裏面會產生想遞歸同樣的調用。但願看完這張圖以後,對你理解裝飾者模式有幫助。

使用裝飾者模式以後的黃燜雞米飯結帳系統,在新增配菜或者產品時,咱們不須要修改原先的功能,只須要對類進行擴展就行了,這徹底遵循了開放-關閉原則

裝飾者模式的優勢

  • 裝飾類和被裝飾類能夠獨立發展,而不會互相耦合,換句話說,就是Component類無須知道Decorator類,Decorator類也不用知道具體的被裝飾者。
  • 裝飾者模式是繼承關係的一個替代方案,從上面的黃燜雞米飯的案例中,咱們能夠看出,無論裝飾多少層,返回的對象仍是Component
  • 裝飾者模式能夠動態的擴展一個實現類的功能

裝飾者模式的優勢

  • 多層裝飾模式比較複雜,你能夠想象一下剝洋蔥,若是最裏面的裝飾出了問題,你的工做量會有多大?

最後多說一句,JDK 中的 java.io 就是用裝飾者模式實現的,有興趣的能夠去深刻了解一下。

源代碼

文章不足之處,望你們多多指點,共同窗習,共同進步

最後

打個小廣告,歡迎掃碼關注微信公衆號:「平頭哥的技術博文」,一塊兒進步吧。

平頭哥的技術博文
相關文章
相關標籤/搜索