設計模式(十一)----結構型模式之裝飾者模式

2023-02-25 21:00:38

1、概述

我們先來看一個快餐店的例子。

快餐店有炒麵、炒飯這些快餐,可以額外附加雞蛋、火腿、培根這些配菜,當然加配菜需要額外加錢,每個配菜的價錢通常不太一樣,那麼計算總價就會顯得比較麻煩。

使用繼承的方式存在的問題:

  • 擴充套件性不好

    如果要再加一種配料(火腿腸),我們就會發現需要給FriedRice和FriedNoodles分別定義一個子類。如果要新增一個快餐品類(炒河粉)的話,就需要定義更多的子類。

  • 產生過多的子類

定義:

指在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能)的模式。

2、結構

裝飾(Decorator)模式中的角色:

  • 抽象構件(Component)角色 :定義一個抽象介面以規範準備接收附加責任的物件。上圖中的抽象快餐類

  • 具體構件(ConcreteComponent)角色 :實現抽象構件,通過裝飾角色為其新增一些職責。炒米粉炒麵等具體角色

  • 抽象裝飾(Decorator)角色 : 繼承或實現抽象構件,幷包含具體構件的範例,可以通過其子類擴充套件具體構件的功能。加雞蛋加火腿抽象類

  • 具體裝飾(ConcreteDecorator)角色 :實現抽象裝飾的相關方法,並給具體構件物件新增附加的責任。具體加雞蛋還是加火腿

3、案例

我們使用裝飾者模式對快餐店案例進行改進,體會裝飾者模式的精髓。

類圖如下:

程式碼如下:

//快餐介面
public abstract class FastFood {
    private float price;
    private String desc;
​
    public FastFood() {
    }
​
    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }
​
    public void setPrice(float price) {
        this.price = price;
    }
​
    public float getPrice() {
        return price;
    }
​
    public String getDesc() {
        return desc;
    }
​
    public void setDesc(String desc) {
        this.desc = desc;
    }
​
    public abstract float cost();  //獲取價格
}
​
//炒飯
public class FriedRice extends FastFood {
​
    public FriedRice() {
        super(10, "炒飯");
    }
​
    public float cost() {
        return getPrice();
    }
}
​
//炒麵
public class FriedNoodles extends FastFood {
​
    public FriedNoodles() {
        super(12, "炒麵");
    }
​
    public float cost() {
        return getPrice();
    }
}
​
//配料類  裝飾者
public abstract class Garnish extends FastFood {
​
    private FastFood fastFood;
​
    public FastFood getFastFood() {
        return fastFood;
    }
​
    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }
​
    public Garnish(FastFood fastFood, float price, String desc) {
        super(price,desc);
        this.fastFood = fastFood;
    }
}
​
//雞蛋配料
public class Egg extends Garnish {
​
    public Egg(FastFood fastFood) {
        super(fastFood,1,"雞蛋");
    }
​
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }
​
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}
​
//培根配料
public class Bacon extends Garnish {
​
    public Bacon(FastFood fastFood) {
​
        super(fastFood,2,"培根");
    }
​
    @Override
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }
​
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}
​
//測試類
public class Client {
    public static void main(String[] args) {
        //點一份炒飯
        FastFood food = new FriedRice();
​
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
​
        System.out.println("===============");
​
        //在上面的炒飯中加一個雞蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
​
        System.out.println("================");
        //再加一個雞蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
​
        System.out.println("================");
        food = new Bacon(food);
        System.out.println(food.getDesc() + "  " + food.cost() + "元");
    }
}

測試結果

好處:

  • 裝飾者模式可以帶來比繼承更加靈活性的擴充套件功能,使用更加方便,可以通過組合不同的裝飾者物件來獲取具有不同行為狀態的多樣化的結果。裝飾者模式比繼承更具良好的擴充套件性,完美的遵循開閉原則,繼承是靜態的附加責任,裝飾者則是動態的附加責任。

  • 裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。

4、使用場景

  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。

    不能採用繼承的情況主要有兩類:

    • 第一類是系統中存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;

    • 第二類是因為類定義不能繼承(如final類)

  • 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。

  • 當物件的功能要求可以動態地新增,也可以在動態地復原時。比如雞蛋已經賣完了,這時候只需要移除雞蛋類就可以了。

5、JDK原始碼解析

IO流中的包裝類使用到了裝飾者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

我們以BufferedWriter舉例來說明,先看看如何使用BufferedWriter

public class Demo {
    public static void main(String[] args) throws Exception{
        //建立BufferedWriter物件
        //建立FileWriter物件
        FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);
​
        //寫資料
        bw.write("hello Buffered");
​
        bw.close();
    }
}

使用起來感覺確實像是裝飾者模式,接下來看它們的結構:

小結:

BufferedWriter使用裝飾者模式對Writer子實現類進行了增強,新增了緩衝區,提高了寫資料的效率。