裝飾器模式(從放棄到入門)

前面介紹了兩篇設(shè)計模式,策略模式和觀察者模式,其實自己也是在學(xué)習(xí)階段,感覺收益很大。所以想繼續(xù)分享,把java中的23中設(shè)計模式都總結(jié)一遍,在以后才能在實踐中靈活運用。感興趣的童鞋可以看看前面分享的兩篇:

策略模式
觀察者模式

前面兩篇都是上來就是例子,需求,我想改變一下套路,今天先介紹裝飾器的理論結(jié)構(gòu),再說例子。還是要再聲明:例子來自于《HeadFirst 設(shè)計模式》,推薦大家看看原書,寫得很淺顯易懂。

理論知識

6.png

實際問題

今天的例子是一個 coffe 的例子,相信大家都去星巴克喝過咖啡,或者奶茶,我們在買coffe的時候,首先選擇一個基本的coffe類型,比如 卡布奇洛,然后添加各種佐料:摩卡,豆?jié){,蒸奶等。基本類型是基礎(chǔ)價,佐料又要寧外算錢。(真是聰明)

現(xiàn)在當(dāng)前的系統(tǒng)設(shè)計是:

1.png

本來想自己畫UML圖,發(fā)現(xiàn)自己畫也一樣,還損失了書上一些重要信息,所以直接盜圖好了,大家不要介意。
Beverage: 飲料,抽象類,getDescription() 返回描述(類型,佐料...),cost() 抽象方法,每種飲料話費不一樣,所以子類自己去實現(xiàn)。
然后派生了4中飲料的子類: HoseBlend, DarkRoast, Decaf, Espresso(尼瑪,我都沒喝過),分別實現(xiàn) cost方法,返回價格。

代碼很簡單,這里就不貼了,然后這個時候,如果飲料有100種,呵呵,這種設(shè)計類圖就是這樣:

2.png

類爆炸!這種設(shè)計,明顯重用率太低,好吧,換種思路,我們把所有的佐料的放到公共父類中,讓所有子類都擁有所有的佐料,只是在類中判斷到底加沒加,例如這樣:

3.png

代碼:

Beverage .java

public abstract class Beverage {
    protected String description;
    
    protected boolean milk;
    protected boolean soy;
    protected boolean mocha;
    protected boolean whip;
    
    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

HoseBlend.java

public class HoseBlend extends Beverage {

    public HoseBlend() {
        description = "HoseBlend";

        milk = true;
        soy = false;
        mocha = false;
        whip = false;
    }

    public double cost() {
        double money = 10.0;
        if (milk)
            money += 2.0;
        if (soy)
            money += 3.0;
        if (mocha)
            money += 3.0;
        if (whip)
            money += 2.0;
        return money;
    }
}

使用:

Beverage hoseBlend = new HoseBlend();
System.out.println(hoseBlend.getDescription());
System.out.println(hoseBlend.cost());

其他類省略了,當(dāng)然這里寫得不規(guī)范,每種佐料的價格應(yīng)該寫成常量,這里直接用了數(shù)字,這里不是重點。這樣的類設(shè)計出來,我們可以用一段話來描述:一個抽象的Beverage類,里面包含了很多佐料,子類決定是否添加這些佐料,并且計算初始價格和佐料價格。

與前一種不同的是,這種更加規(guī)范,父類決定了所有的佐料和價格,只是你填不填加,自己決定。突然想到了一種更好的方法,為何不讓Beverage去計算價格呢?

Beverage.java

public abstract class Beverage {
    protected String description;
    protected double money;
    
    public Beverage(){
        this.description = "unknown beverage";
        this.money = 10.0;
    }

    public String getDescription() {
        return description;
    }
    
    public double cost(){
        return money;
    }

    public void addMilk() {
        this.money += 2.0;
    }
    
    public void addSoy(){
        this.money += 3.0;
    }
    
    public void addMocha(){
        this.money += 3.0;
    }
    
    public void addWhip(){
        this.money += 2.0;
    }
}

添加4中 addXXX() 方法,然后計算money,將計算價格留給父類,子類只需要添加佐料:

HoseBlend .java

public class HoseBlend extends Beverage {
    public HoseBlend() {
        description = "HoseBlend";
        addMilk();
    }
}

感覺這樣寫重用可以更好,并且子類工作也更少了。呵呵,書上沒寫,自己瞎想的。但是上面的這種設(shè)計,當(dāng)遇到添加一種佐料時,都必須修改Beverate類,在設(shè)計模式原則中有一個非常重要的原則,就是開閉原則:對擴(kuò)展開放,對修改關(guān)閉。上面的幾種設(shè)計都存在一定的問題。我們來看看今天的主角,裝飾器模式,怎么來完成。

裝飾器模式

先來看一張形象的圖:

4.png

最里層的 DarkRoast 是飲料類型外面添加一層 Mocha, 再外層添加 Whip。最終cost() 就從最外層,一直調(diào)用到最里層,累加得到價格,呵呵,是不是有點像遞歸,對多態(tài)的遞歸,看看類結(jié)構(gòu):

5.png

當(dāng)然這是在最初,我們類爆炸那個例子中的結(jié)構(gòu)擴(kuò)展的:
Beverage依然是那個抽象類,依然有4種飲料繼承與它。不同的是,多了一個 CondimentDecorator,繼承于 Beverage,然后4種佐料都繼承與 CondimentDecorator,并都包含一個 beverage 的引用,表示自己裝飾的對象。

好了,看看代碼:

Beverage .java 還是長這樣

public abstract class Beverage {
    protected String description;
    
    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

HouseBlend .java 第一種飲料,最低10.0元

public class HouseBlend extends Beverage {
    
    public HouseBlend(){
        description = "HoseBlend";
    }

    public double cost() {
        return 10.0;
    }
}

DarkRoast.java 第二種飲料,最低12.0元

public class DarkRoast extends Beverage {
    
    public DarkRoast(){
        description = "DarkRoast";
    }

    public double cost() {
        return 12.0;
    }
}

CondimentDecorator .java 讓子類都重寫getDescription() 方法

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

Milk.java : 牛奶,每加一份2.0 元

public class Milk extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Milk";
    }

    public double cost() {
        return this.beverage.cost() + 2.0;
    }
}

Mocha.java 摩卡,每份3.0元

public class Mocha extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Mocha";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

Soy.java 醬油,每份3.0元

public class Soy extends CondimentDecorator {

    Beverage beverage;

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

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Soy";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

好了,看看我們怎么調(diào)用:

public class Main {
    public static void main(String[] args) {
        Beverage b1 = new HouseBlend();
        System.out.println(b1.getDescription() + " $" + b1.cost());
        
        Beverage b2 = new DarkRoast();
        b2 = new Mocha(b2);
        b2 = new Soy(b2);
        b2 = new Milk(b2);
        System.out.println(b2.getDescription() + " $" + b2.cost());
    }
}

輸出:

HoseBlend $10.0
DarkRoast,Mocha,Soy,Milk $20.0

總結(jié)

  1. 解耦合了吧,飲料和佐料分開,想怎么加怎么加,如果要添加新飲料或者佐料,只需繼續(xù)添加,而無需修改以前的結(jié)構(gòu)。這就是對擴(kuò)展開放,對修改關(guān)閉。
  2. 我前面提到了一句話:遞歸多態(tài),哈哈哈,自己瞎編的!為什么會出現(xiàn)這種結(jié)果,如果你對多態(tài)熟悉的話,就很好理解了,看上面代碼的 b2:
  • 為什么 Milk 可以賦值給 Beverage , 因為 Milk 的父類繼承于 Beverage
  • b2調(diào)用 cost() 是調(diào)用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一個b2,及 Soy, Soy調(diào)用cost(), 及調(diào)用 Mocha.cost() + 3.0 ... , 知道最后調(diào)用 beverage 的 cost() , 是不是很像遞歸。

再一句話概括裝飾者模式吧:

裝飾者模式,就是裝飾者(Docorator)需要繼承與被裝飾者(Component),并且持有被裝飾者的引用(Component),從而可以通過復(fù)寫,在原來 Component 的基礎(chǔ)上對方法做一定的修飾。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容