文章目錄
- 定義
- 案例1:三點幾啦
- 首次嘗試
- 再次嘗試
- 設計原則:類應該對擴展開放,對修改關閉
- 嘗用裝飾者模式
- 裝飾者模式特征
- 本例的類圖
- 放碼過來
- 飲料類
- HouseBlend
- DarkRoast
- Espresso
- Decaf
- 調料裝飾類
- Milk
- Mocha
- Soy
- Whip
- 運行測試類
- 案例2:編寫自己的Java I/0裝飾者
- JDK中的IO流概述
- 放碼過來
- LowerCaseInputStream
- 創建一測試文件D:\\test.txt
- 運行測試類
- 參考資料
定義
裝飾者(Decorator)模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
案例1:三點幾啦
更新咖啡連鎖店的訂單系統,原先類的設計:
咖啡店為拓展業務,允許顧客在飲料上添加各種調料,如:
- 蒸奶 Steamed Milk
- 豆漿 Soy
- 摩卡(巧克力風味) Mocha
- 覆蓋奶泡
加入的調料收取不同的費用。
首次嘗試
類數量爆炸
這違背嚴重兩條設計原則:
- 多用組合,少用繼承。
- 為了交互對象之間的松耦合設計而努力。
再次嘗試
利用實例變量和繼承,追蹤這些調料:
這次嘗試的局限性:
- 調料價錢的改變會使我們更改現有代碼。
- 一旦出現新的調料,我們就需要加上新的方法,并改變超類中的cost()方法。
- 以后可能會開發出新飲料。對這些飲料而言(例如:冰茶),某些調料可能并不適合,但是在這個設計方式中,Tea(茶)子類仍將繼承那些不適合的方法,例如:hasWhip()(加奶泡)。
- 萬一顧客想要雙倍摩卡咖啡,怎么辦?
設計原則:類應該對擴展開放,對修改關閉
我們的目標是允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行為。
如能實現這樣的目標,這樣的設計具有彈性可以應對改變,可以接受新的功能來應對改變的需求。
雖然似乎有點矛盾,但是的確有一些技術可以允許在不直接修改代碼的情況下對其進行擴展。
在選擇需要被擴展的代碼部分時要小心。每個地方都采用開放-關閉原則,是一種浪費,也沒必要,還會導致代碼變得復雜且難以理解。
(MyNote:隨機應變)
嘗用裝飾者模式
我們要以飲料為主體,然后在運行時以調料來“裝飾”(decorate)飲料。比方說,如果顧客想要摩卡
和奶泡深焙咖啡,那么,要做的是:
1.拿一個深焙咖啡(DarkRoast)對象
2.以摩卡(Mocha)對象裝飾它
3.以奶泡(Whip)對象裝飾它
4.調用cost()方法,并依賴委托(delegate)將調料的價
錢加上去。
裝飾者模式特征
-
裝飾者和被裝飾對象有相同的超類型。
-
你可以用一個或多個裝飾者包裝一個對象。
-
既然裝飾者和被裝飾對象有相同的超類型,所以在任何需要原始對象(被包裝的)的場合,可以用裝飾過的對象代替它。
-
裝飾者可以在所委托被裝飾者的行為之前與/或之后,加上自己的行為,以達到特定的目的。
-
對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象。
本例的類圖
放碼過來
飲料類
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() {return description;}public abstract double cost();
}
HouseBlend
public class HouseBlend extends Beverage {public HouseBlend() {description = "House Blend Coffee";}public double cost() {return .89;}
}
DarkRoast
public class DarkRoast extends Beverage {public DarkRoast() {description = "Dark Roast Coffee";}public double cost() {return .99;}
}
Espresso
public class Espresso extends Beverage {public Espresso() {description = "Espresso";}public double cost() {return 1.99;}
}
Decaf
public class Decaf extends Beverage {public Decaf() {description = "Decaf Coffee";}public double cost() {return 1.05;}
}
調料裝飾類
public abstract class CondimentDecorator extends Beverage {Beverage beverage;public abstract String getDescription();
}
Milk
public class Milk extends CondimentDecorator {public Milk(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Milk";}public double cost() {return .10 + beverage.cost();}
}
Mocha
public class Mocha extends CondimentDecorator {public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return .20 + beverage.cost();}
}
Soy
public class Soy extends CondimentDecorator {public Soy(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Soy";}public double cost() {return .15 + beverage.cost();}
}
Whip
public class Whip extends CondimentDecorator {public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}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());System.out.println("---");Beverage beverage2 = new DarkRoast();beverage2 = new Mocha(beverage2);beverage2 = new Mocha(beverage2);beverage2 = new Whip(beverage2);System.out.println(beverage2.getDescription() + " $" + beverage2.cost());System.out.println("---");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.99
---
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
---
House Blend Coffee, Soy, Mocha, Whip $1.34
案例2:編寫自己的Java I/0裝飾者
JDK中的IO流概述
java.io包內的類中許多類都是裝飾者。
下面是一個典型的對象集合,用裝飾者來將功能結合起來,
以讀取文件數據:
BufferedInputStream及LineNumberInputStream都擴展自FilterInputStream,而FilterInputStream是一個抽象的裝飾類。
java.io其實沒有多大的差異。我們把java.io API范圍縮小,讓你容易查看它的文件,并組合各種“輸入”流裝飾者來符合你的用途。
你會發現輸出流(OutputStream)的設計方式也是一樣的。你可能還會發現Reader/Writer流(作為基于字符數據的輸入輸出)和輸入流/輸出流的類相當類似(雖然有一些小差異和不一致之處,但是相當雷同,所以你應該可以了解這些類)。
但是Java I/O也引出裝飾者模式的一個缺點:利用裝飾者模式,常常造成設計中有大量的小類,數量實在太多,可能會造成使用此API程序員的困擾。
但是,現在你已經了解了裝飾者的工作原理,以后當使用別人的大量裝飾的API時,就可以很容易地辨別出他們的裝飾者類是如何組織的,以方便用包裝方式取得想要的行為。
放碼過來
這個想法怎么樣:編寫一個裝飾者,把輸入流內的所有大寫字符轉成小寫。
舉例:當讀取“I know the Decorator Pattern thereforeI RULE!”,裝飾者會將它轉成“i know thedecorator pattern therefore i rule ! ”
LowerCaseInputStream
擴展FilterInputStream,這是所有InputStream的抽象裝飾者:
import java.io.*;public class LowerCaseInputStream extends FilterInputStream {public LowerCaseInputStream(InputStream in) {super(in);}public int read() throws IOException {int c = in.read();return (c == -1 ? c : Character.toLowerCase((char)c));}public int read(byte[] b, int offset, int len) throws IOException {int result = in.read(b, offset, len);for (int i = offset; i < offset+result; i++) {b[i] = (byte)Character.toLowerCase((char)b[i]);}return result;}
}
創建一測試文件D:\test.txt
I know the Decorator Pattern therefore I RULE!
運行測試類
import java.io.*;public class InputTest {public static void main(String[] args) throws IOException {int c;InputStream in = null;try {in = new LowerCaseInputStream( new BufferedInputStream(new FileInputStream("D:\\text.txt")));while((c = in.read()) >= 0) {System.out.print((char)c);}} catch (IOException e) {e.printStackTrace();} finally {if (in != null) { in.close(); }}System.out.println();System.out.println("---");//try (InputStream in2 = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("D:\\text.txt")))) {while((c = in2.read()) >= 0) {System.out.print((char)c);}} catch (IOException e) {e.printStackTrace();}}
}
運行結果:
i know the decorator pattern therefore i rule!
---
i know the decorator pattern therefore i rule!
參考資料
- 《Head First 設計模式》