裝飾者模式

GitHub代碼

場景描述:咖啡店的訂單系統,初始的類設計如下
在這裏插入圖片描述
現提供加入配料的服務,在原有的系統上進行擴展。

初次嘗試:直接爲每個組合創建一個類,帶來的問題就是類的數量過多,且毫無彈性可言。
在這裏插入圖片描述
再次嘗試:從基類Beverage下手,通過實例變量表示是否加上調料。
在這裏插入圖片描述
帶來的問題:如果發生需求變動,比如調整調料價格,加入新的調料等等都會造成對原有代碼的修改。也沒有實現彈性設計。

設計原則:類應該對擴展開放,對修改關閉。

我們的目標是允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行爲。

上述原則叫做“開放-關閉”原則,遵循開放-關閉原則,通常會引入新的抽象層次,增加代碼的複雜度。
每個地方都採用開放-關閉原則,是一種浪費,也沒必要,還會導致代碼變得複雜且難以理解。

我們看一下裝飾者模式,使用它來解決我們的問題。

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

在這裏插入圖片描述
裝飾者和被裝飾對象有相同地超類型。
你可以用一個或多個裝飾者包裝一個對象。
裝飾者可以在所委託被裝飾者地行爲之前與/或之後,加上自己的行爲,以達到特定的目的。

那麼試着通過裝飾者模式解決我們最開始的問題。
在這裏插入圖片描述
那麼我們看一下相關的代碼實現。
首先是基類Beverage

public abstract class Beverage {
    public String description = "Unknown starbuzz.Beverage";

    public String getDescription(){
        return description;
    }

    public abstract double cost();
}

然後看一下裝飾者類

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

然後我們實現一些飲料

public class Espresso extends Beverage {
    public Espresso(){
        description = "starbuzz.coffee.Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {
    public HouseBlend(){
        description = "House Blend Coffee";
    }

    @Override
    public double cost() {
        return .89;
    }
}

接着實現調料類

public class Mocha extends CondimentDecorator {
    Beverage beverage;

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

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

    @Override
    public double cost() {
        return .20 + beverage.cost();
    }
}
public class Soy extends CondimentDecorator {
    Beverage beverage;

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

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

    @Override
    public double cost() {
        return .15 + beverage.cost();
    }
}
public class Whip extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription(){
        return beverage.getDescription() + ", starbuzz.condiment.Whip";
    }

    @Override
    public double cost() {
        return .10 + beverage.cost();
    }
}

然後我們測試一下

public class StarbuzzCoffee {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        Beverage beverage2 = new HouseBlend();
        beverage2 = new Soy(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}

得到結果
在這裏插入圖片描述
可以看到我們通過裝飾者模式很好的解決了這個問題。

裝飾者模式的缺點:裝飾者會導致設計中出現許多小對象,如果過度使用,會讓程序變得很複雜。

一個疑問:如果你把代碼寫成依賴於具體的組件類型,那麼裝飾者就會導致程序出問題。
這句話我暫時無法理解,搜了一下,也沒有找到相關講解或者某個具體例子之類的。

在Java中,java.io包中大量使用了裝飾者模式。如
在這裏插入圖片描述
所以我們也可以嘗試自己實現一個io裝飾者來包裝io包中的類。它嘗試把輸入的字節流中的所有大寫都轉換爲小寫。

public class LowerCaseInputStream extends FilterInputStream {
    public LowerCaseInputStream(InputStream in){
        super(in);
    }

    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char) c));
    }

    public int read(byte[] b, int offset, int len) throws IOException {
        int result = super.read(b, offset, len);
        for (int i = offset; i < offset + result; i++){
            b[i] = (byte)Character.toLowerCase((char) b[i]);
        }
        return result;
    }
}

我們測試一下

public class InputTest {
    // 相對於整個項目的文件路徑
    private static String file = "/io_extend/test.txt";
    public static void main(String[] args) {
        int c;
        try {
//            System.out.println(new File(".").getAbsolutePath());
            //當前類的絕對路徑
//            System.out.println(InputTest.class.getResource("/").getFile());
            //指定CLASSPATH文件的絕對路徑
//            System.out.println(InputTest.class.getResource(file).getFile());
            InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream(InputTest.class.getResource(file).getFile())));

            while ((c = in.read()) >= 0){
                System.out.print((char) c);
            }
            in.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

總結:裝飾者模式的使用場景爲,當我們需要在不改動原有代碼的情況下,能夠動態的添加新的功能(行爲)時,就可以使用裝飾者;尤其是java.io包給了我們一個很好的參照,同時也暴露出它的問題,就是會導致類變得特別的多,因爲每新增一個行爲或功能時,都需要進行一次包裝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章