裝飾者模式(5)

定義

動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
裝飾者對象和被裝飾的對象都實現了相同的操作接口,裝飾者將被裝飾者包裝起來,在同名的接口方法中,在調用被裝飾者的方法之前或者之後做一些自己的操作,這樣在外部調用者來看,就相當於被“裝飾”了一樣。

咖啡的例子
首先咖啡是一種飲料,然後咖啡還可以加調料如加糖加牛奶等等。飲料是抽象的構件基類,咖啡作爲具體的構件實現類,調料就是我們抽象的裝飾者基類,然後會有具體的飲料裝飾者實現類。在基類中我們給飲料添加兩個行爲,一個是獲取飲料的描述,一個是獲取飲料的價格。類圖設計如下:

在這裏插入圖片描述
其中Beverage是抽象的飲料類,Coffee是具體的咖啡飲料類,BeverageDecorator是抽象的飲料裝飾者類,SugarCoffee、 LemonCoffee和MilkCoffee是具體的飲料裝飾類,分別表示給咖啡加糖、加檸檬和加牛奶。

相關實現代碼:

/**
 * 飲料抽象類
 */
public abstract class Beverage {
    protected String description = "Unknown Beverage";
    public String getDescription() {
        return description;
    }
    public abstract double getPrice();
}
/**
 * 咖啡飲料
 */
public class Coffee extends Beverage {
    public Coffee() {
        description = "咖啡飲料";
    }
    @Override
    public double getPrice() {
        return 10.00;
    }
}

飲料裝飾者抽象類

/**
 * 飲料裝飾者抽象類
 */
public abstract class BeverageDecorator extends Beverage {
    private Beverage beverage;

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

    @Override
    public String getDescription() {
        return beverage.getDescription();
    }

    @Override
    public double getPrice() {
        return beverage.getPrice();
    }
}
//這個類是可以不要的,直接繼承基類

裝飾者類

/**
 * 裝飾者類,負責給咖啡加糖
 */
public class SugarCoffee extends BeverageDecorator {

    public SugarCoffee(Beverage beverage) {
        super(beverage);
    }

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

    @Override
    public double getPrice() {
        return super.getPrice() + 2.00;
    }
}

/**
 * 裝飾者類,負責給咖啡加牛奶
 */
public class MilkCoffee extends BeverageDecorator {

    public MilkCoffee(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ",加牛奶";
    }

    @Override
    public double getPrice() {
        return super.getPrice() + 3.00;
    }
}
/**
 * 裝飾者類,負責給咖啡加檸檬
 */
public class LemonCoffee extends BeverageDecorator {

    public LemonCoffee(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ",加檸檬";
    }

    @Override
    public double getPrice() {
        return super.getPrice() + 4.00;
    }
}

測試

public class Client {

    public static void main(String[] args) {
        //創建一種叫咖啡的飲料
        Beverage coffee = new Coffee();
        //給咖啡加糖
        coffee = new SugarCoffee(coffee);
        //給咖啡加牛奶
        coffee = new MilkCoffee(coffee);
        //給咖啡加檸檬
        coffee = new LemonCoffee(coffee);

        System.out.println("你點的飲料是:"+coffee.getDescription()+"\n"+"價格是:"+coffee.getPrice()+"元");
    }
}

可以看到,我們要生成各種調料的咖啡,代碼就很簡單,只要調用構造函數,一層一層的往上套就可以了,每加一層飲料修飾,咖啡就具有了新的飲料特性以及售價。並且這裏順序其實也是可以調整的,不過對這個例子而言順序調整沒有太大意義,但在實際當中或許是有用的。

仔細思考,經過多次裝飾的過程中我們的咖啡究竟發生了哪些變化?

以獲取售價爲例,當經過SugarCoffee之後getPrice()會先獲取原始的咖啡售價,然後再加上Sugar自己的售價,最後返回。在經過MilkCoffee裝飾之後,getPrice()則會先獲取經過SugarCoffee裝飾之後的價格,然後加上Milk自己的價格,最後返回。在經過LemonCoffee裝飾之後,getPrice()則會先獲取經過MilkCoffee裝飾之後的價格,然後加上Lemon自己的價格,最後返回。整個過程就像剝洋蔥一樣,層層嵌套,當然貌似有點遞歸的意思在裏面。

通過下面的圖可以幫助你很好的理解裝飾者與原始對象之間的關係:
在這裏插入圖片描述

Java中的裝飾者

Java中的java.io包裏面的InputStream類和OutputStream類就是裝飾者模式。

剛接觸java的時候,相信你一定見過類似下面的代碼:

InputStream inputStream = null;
OutputStream outputStream = null;
try {
    outputStream = new BufferedOutputStream(new FileOutputStream(new File("test2.txt")));
    inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("text.txt"))););
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, length);
    }
} catch (Exception e) {
    e.printStackTrace();
}

實際上在涉及輸入流輸出流的操作時,都會進行一些嵌套使用,如果對裝飾者模式不熟悉,你可能會困惑或者不理解爲什麼java的輸入流要嵌套這麼多層。其實每一層都有自己實現的一些特性:
在這裏插入圖片描述

這些都是裝飾者模式的實現,實際上在java的io包中存在着數量衆多的裝飾者類,單看InputStream就有好多:
在這裏插入圖片描述
優缺點

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

同時也有缺點,那就是裝飾者實現類可能會很多,容易出現類膨脹,需要維護的代碼就要多一些, 並且它們之間往往長得都很相似,比如java中的InputStream類,如果對這些裝飾者類不熟悉的話,可能一時間會陷入不知道該使用哪個裝飾者的尷尬境地。

何時應用裝飾者模式:
當你需要擴展一個類的功能,或者給一個類動態的附加功能,又可以動態的撤銷,或者想在一個方法的執行前/後添加自己的行爲,都可以選擇裝飾者模式。

經典實戰:對讀入的內容字符轉大寫

public class UpperCaseInputStream extends FilterInputStream{

	protected UpperCaseInputStream(InputStream in) {
		super(in);
	}

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

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int result =  super.read(b, off, len);
		for(int i=0;i<result;i++){
			b[i] = (byte) Character.toUpperCase((char)b[i]);
		}
		return result;
	}
}

測試:

public class Tests {
	public static void main(String[] args) throws IOException {
		int c = 0;
		try {
			InputStream in = new UpperCaseInputStream
					(new BufferedInputStream(new FileInputStream(new File("d:/data/aa.txt"))));
			while((c=in.read())>0){
				System.out.println((char)c);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章