Java設計模式之裝飾模式詳解

裝飾者模式是動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

假設咱們有一個需求,是給一家飲料店作一個計算各類飲料價格的功能。聽起來很簡單,咱們建立一個抽象父類Beverages,description用來描述飲料名字,price方法用來計算飲料的價格。ide

public abstract class Beverages {
    private String description;

    public String getDescription() {
        return description;
    }

    public abstract double price();
}

每種飲料咱們都建立一個子類去繼承父類,賦值給description,而且重寫price方法來設置本身的價格。看上去很完美,咱們運行一下看看結果。函數

public class Coffee extends Beverages {

    public Coffee(String description){
        this.description = description;
    }

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

    @Override
    public double price() {
        return 3.5;
    }
}

public static void main(String[] args){
    Coffee coffee = new Coffee("咖啡");
    System.out.println(coffee.getDescription());
    System.out.println(coffee.price());
}

結果:
咖啡
3.5this


可是問題來了,飲料店裏不單單隻有一種飲料,還有可樂、七喜、奶茶其餘各類各樣的飲料,難道咱們要每一個都建立一個子類嗎?好吧,就算你以爲幾十種飲料還不算多,那若是各類飲料直接再進行搭配呢?咖啡加牛奶、咖啡加巧克力、加糖、不加糖,由於各類配料的不一樣價格和描述也不一樣,個人天呀,難道要建立幾百個類嗎?這個時候咱們忽然想到了不是能夠用繼承來解決這個問題嗎?那咱們就來試一下。設計


咱們改造一下Beverages類,把要加的配料用boolean值聲明,若是須要添加配料就調用set方法設置爲true,在price方法中計算的時候來判斷哪一些配料添加了須要計算價格。code

public class Beverages {
    public String description;

    public boolean milk;
    public boolean sugar;

    public double milkPrice = 1.5;
    public double sugarPrice = 0.5;

    public boolean isMilk() {
        return milk;
    }

    public void setMilk(boolean milk) {
        this.milk = milk;
    }

    public boolean isSugar() {
        return sugar;
    }

    public void setSugar(boolean sugar) {
        this.sugar = sugar;
    }

    public String getDescription() {
        return description;
    }

    public double price(){
        double basePrice = 0.0;
        if (isMilk()){
            basePrice += milkPrice;
        }
        if (isSugar()){
            basePrice += sugarPrice;
        }
        return basePrice;
    }
}

而後咱們在子類計算價格的時候加上父類中計算好的配料的價格。對象

public class CoffeeWithMilk extends Beverages {

    public CoffeeWithMilk(String description){
        this.description = description;
    }

    @Override
    public double price() {
        return 3.0 + super.price();
    }
}

咱們運行看一下,咖啡加牛奶加糖,結果沒有問題。繼承

CoffeeWithMilk coffeeWithMilk = new CoffeeWithMilk("咖啡加牛奶加糖");
coffeeWithMilk.setMilk(true);
coffeeWithMilk.setSugar(true);
System.out.println(coffeeWithMilk.getDescription());
System.out.println(coffeeWithMilk.price());

結果:
咖啡加牛奶加糖
5.0ip


這樣一來咱們解決了新建無數個子類的問題,可是咱們發現單純繼承的作法仍是有太多弊端,好比說若是咱們想要新添加配料,咱們得修改在父類Beverages中添加新的調料字段,還要修改price方法,這嚴重違反了開發-關閉的設計原則,類應該對擴展開發,對修改關閉。並且有些飲料和配料是沒辦法搭配的,例如啤酒加糖,可是子類仍是會繼承到這些配料,而且若是是要同一份配料要加雙份又該怎麼改呢?因此單純繼承的方法仍是不行。這時候咱們就要使用到裝飾者模式。開發


首先咱們建立抽象父類Beverages,這個父類只是飲料的父類,不是配料的父類。咱們建立一個Cola類直接繼承它。get

public abstract class Beverages {
    public String description;

    public String getDescription() {
        return description;
    }

    public abstract double price();
}

public class Cola extends Beverages {

    public Cola(String description) {
        this.description = description;
    }

    @Override
    public double price() {
        return 2.0;
    }
}

如今就缺配料的部分的代碼了,咱們再建立一個配料的抽象類Seasonings,咱們讓它繼承Berverages類,而且聲明瞭一個Berverages的引用和抽象的getDescription方法,這裏咱們稍後再做解釋。

public abstract class Seasonings extends Beverages{
    public Beverages beverages;
    public abstract String  getDescription();
}

接下來咱們看看配料的實現類怎麼寫。咱們讓他繼承了父類Seasonings,構造函數接收父類Beverages類型,其實也就是要被裝飾的對象,也就是各類各樣須要加配料的飲料。而後咱們重寫了getDescription,咱們在傳進來的那個Beverages對象的基礎上添加名字,price方法也是同理。這樣其實就在給傳進來的對象外面作了一層裝飾,也就是給飲料添加了配料。

public class Ice extends Seasonings {

    public Ice(Beverages beverages) {
        this.beverages = beverages;
    }

    @Override
    public String getDescription() {
        return beverages.getDescription() + "加冰";
    }

    @Override
    public double price() {
        return beverages.price() + 0.2;
    }
}

運行一下,看看結果。沒有問題,能夠搭配成功。

Cola cola = new Cola("可樂");

Beverages ice = new Ice(cola);
System.out.println(ice.getDescription());
System.out.println(ice.price());

結果:
可樂加冰
2.2


咱們還想再添加別的配料,再建立一個新的配料。

public class Sugar extends Seasonings {

    public Sugar(Beverages beverages) {
        this.beverages = beverages;
    }

    @Override
    public String getDescription() {
        return beverages.getDescription() + "加糖";
    }

    @Override
    public double price() {
        return 0.5 + beverages.price();
    }
}

咱們在原來搭配好的基礎上再進行裝飾,結果也是沒有問題的。這個時候就要提到爲何咱們的Seasonings類要繼承Beverages類了,由於每一個配料的實現類裏有一個Beverages類型的引用,這樣咱們才能夠對它的子類進行裝飾,咱們讓Seasonings也繼承Beverages那它的子類也是Beverages類型,咱們就能夠想裝飾幾層就裝飾幾層。

Beverages ice = new Ice(cola);
System.out.println(ice.getDescription());
System.out.println(ice.price());

Beverages sugar = new Sugar(ice);
System.out.println(sugar.getDescription());
System.out.println(sugar.price());

結果:
可樂加冰
2.2
可樂加冰加糖
2.7


在Java的類庫中就有不少實際應用到了裝飾模式,好比BufferedInputStream就能夠用來裝飾FileInputStream,提供更增強大的功能。


總結:裝飾模式就是裝飾者和被裝飾者都具備相同的超類,裝飾者拿到被裝飾者的引用以後,在調用被裝飾者的方法的同時再加上本身的新功能,從而實現了功能的增長,也不須要修改原來的代碼。並且由於是相同的超類,因此能夠裝飾不少層。

相關文章
相關標籤/搜索