詳細內容參見《HeadFirst設計模式》第三章 3裝飾者模式裝飾對象
裝飾者模式,動態地將責任附加到對象上,若要擴充功能,裝飾者提供了比繼承更有彈性的替代方案。
以咖啡店為例。
(1) 裝飾者和被裝飾者對象有相同的超類型。
(2) 可以用一個或多個裝飾者封裝一個對象。
(3) 既然裝飾者和被裝飾者對象有相同的超類型,所以在任何需要原是對象(被封裝的)的場合,可以用裝飾過的對象代替它。
(4) 裝飾者可以在所委託被裝飾者的行為之前與/或之後,加上自己的行為,以達到特定的目的。
(5) 對象可以在任何時候被裝飾,所以可以再運行時動態地、不限量地用你喜歡的裝飾者類裝飾對象。
0. 設計原則
類應該對擴充開放,對修改關閉。
1. 抽象的咖啡基類。
// Beverage是一個抽象類別,有兩個方法:getDescription()和cost()。public abstract class Beverage { String description = "Unknown Beverage"; // getDescription()已經在此實現了,但是cost()必須在子類中實現。 public String getDescription() { return description; } public abstract double cost();}
2. 4種具體的咖啡,繼承自Berverage基類。
// 首先,讓Espresso擴充自Beverage類,因為Espresso是一種飲料。public class Espresso extends Beverage { // 為了要設定飲料的描述,我們寫了一個構造器,記住,description執行個體變數繼承自Beverage類。 public Espresso() { description = "Espresso"; } // 最後,需要計算Espresso的價錢,現在不需要管調料的價錢,直接把Espresso的價格$1.99返回即可。 public double cost() { return 1.99; }}
public class HouseBlend extends Beverage { public HouseBlend() { description = "House Blend Coffee"; } public double cost() { return .89; }}
public class DarkRoast extends Beverage { public DarkRoast() { description = "Dark Roast Coffee"; } public double cost() { return .99; }}
public class Decaf extends Beverage { public Decaf() { description = "Decaf Coffee"; } public double cost() { return 1.05; }}
3. 調料的抽象基類,繼承自Beverage基類。
// 首先,必須讓CondimentDecorator能夠取代Beverage,所以將CondimentDecorator擴充自Beverage類。public abstract class CondimentDecorator extends Beverage { // 所有的調料裝飾類都必須重新實現getDescription()方法。 public abstract String getDescription();}
4. 4中調料裝飾者類,繼承自CondimentDecorator基類。注意,他們除了必須實現cost()之外,還必須實現getDescription()。
// 摩卡是一個裝飾者,所以它擴充紫CondimentDecorator。public class Mocha extends CondimentDecorator {// 別忘了,CondimentDecorator擴充自Beverage。 // 要讓Mocha能夠引用一個Beverage,做法如下: // (1)用一個執行個體變數記錄飲料,也就是被裝飾者。 // (2)想辦法讓被裝飾者(飲料)被記錄在執行個體變數中。這裡的做法是:把飲料當作構造器的參數,再由構造器將此飲料記錄在執行個體變數中。 Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } public String getDescription() { // 我們希望敘述不只是描述飲料(例如“DarkRoast”),而是完整地連調料都描述出來(例如“DarkRoast,Mocha”)。 // 所以首先利用委託的做法,得到一個敘述,然後在其後加上附加的敘述(例如“Mocha”)。 return beverage.getDescription() + ", Mocha"; } public double cost() { // 要計算帶Mocha飲料的價錢。首先把調用委託給被裝飾對象,以計算價錢,然後再加上Mocha的價錢,得到最後結果。 return .20 + beverage.cost(); }}
public class Milk extends CondimentDecorator { Beverage beverage; public Milk(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Milk"; } public double cost() { return .10 + beverage.cost(); }}
public class Soy extends CondimentDecorator { Beverage beverage; public Soy(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Soy"; } 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() + ", Whip"; } public double cost() { return .10 + beverage.cost(); }}
5. 所有類的關係架構圖。
6. 測試代碼。
public class StarbuzzCoffee { public static void main(String args[]) { // 訂一杯Espresso,不需要調料,列印出它的描述和價錢。 Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); // 製造一個DarkRoast對象。 Beverage beverage2 = new DarkRoast(); // 用Mocha裝飾它。 beverage2 = new Mocha(beverage2); // 用第二個Mocha裝飾它。 beverage2 = new Mocha(beverage2); // 用Whip裝飾它。 beverage2 = new Whip(beverage2); System.out .println(beverage2.getDescription() + " $" + beverage2.cost()); // 最後,再來一杯調料為豆漿,摩卡和奶泡的HouseBlend咖啡。 Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out .println(beverage3.getDescription() + " $" + beverage3.cost()); }}
列印的結果。
Espresso $1.99Dark Roast Coffee, Mocha, Mocha, Whip $1.49House Blend Coffee, Soy, Mocha, Whip $1.34
7. java的io包採用的就是裝飾者模式。
自訂的FilterInputStream子類。
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 { public static void main(String[] args) throws IOException { int c; try { InputStream in = new LowerCaseInputStream(new BufferedInputStream( new FileInputStream("test.txt"))); while ((c = in.read()) >= 0) { System.out.print((char) c); } in.close(); } catch (IOException e) { e.printStackTrace(); } }}