Header First設計模式——裝飾者模式
我曾經以為應該用繼承處理一切。後來領教到執行時擴充套件,遠比編譯時期的繼承威力大。本章可以稱為“給愛用繼承的人一個全新的設計眼界”。我們即將再度探討典型的繼承濫用問題。在本章將會學到如何使用物件組合的方式,做到在執行時裝飾類。一旦你熟悉了裝飾的技巧,你將能在不修改任何底層程式碼的情況下,給你的(或別人的)物件賦予新的職責。 新的例題:星巴茲是以狂戰速度最快而聞名的咖啡連鎖店。因為擴張速度實在太快,他們準備更新訂單系統,以合乎他們的飲料供應要求。他們原來的類設計是這樣:
購買咖啡時,也可以要求在其中加入各種調料,例如:蒸奶,豆漿,摩卡或覆蓋奶泡。星巴茲會根據所加入的調料收取不同的費用。所以訂單系統必須考慮到這些調料部分。這是他們的第一個嘗試:
接著,我們可能會想到繼承,在基類Beverage中,加上例項變數代表是否加上調料。但是這樣也會帶來一些問題,例如: 1、一旦出現新的調料,我們就需要新增新的方法,並改變超類中cost()方法。
2、對於一些飲料,可能並不希望繼承別的飲料方法。
因此,利用繼承無法完全解決問題,在星巴克遇到的問題有:類數量爆炸、設計死板、以及基類加入的新功能並不適用於所有的子類。為了不違背開放—關閉原則:類應該對擴充套件開放,對修改關閉。所以在這裡我們要以飲料為主體,然後在執行時以調料來“裝飾”飲料,那麼要做的是:
1:拿一個深焙咖啡(DarkRoast)物件。
2:以摩卡(Mocha)物件裝飾它。
3:以奶泡(Whip)物件裝飾它。
4:呼叫cost()方法,並依賴委託(delegate)將調料的價錢加上去。
但是如何“裝飾”一個物件,而“委託”又要如何與此搭配使用?
以裝飾者構造飲料訂單 1:以DarkRoast物件開始。DarkRoast繼承自Beverage,且有一個用來計算飲料價錢的cost()方法。 2:顧客想要摩卡(Mocha),所以建立一個Mocha物件,並用它將DarkRoast物件包(wrap)起來。Mocha物件是一個裝飾者,他的型別“反應”了它所裝飾的物件(本例中就是Beverage)。所謂的“反應”,指的就是兩者型別一致。 3:顧客也想要奶泡(Whip),所以需要建立一個Whip裝飾者,並用它將Mocha物件包裝起來。別忘了,DarkRoast繼承自Beverage,並有一個cost()方法,用來計算飲料價錢。所以,被Mocha和Whip包起來的DarkRoast物件仍然可以具有DarkRoast的一切行為,包括呼叫它的cost()方法。 4:現在,該是顧客算錢的時候了,通過呼叫最外圈裝飾者(Whip)的cost()就可以辦得到。Whip的cost()會先委託它裝飾的物件(也就是Mocha)計算出價錢,然後再加上奶泡的價錢。到此。我們知道了:
裝飾者我們的飲料 讓星巴茲飲料也能符合此框架
具體實現:
從飲料下手,將飲料作為抽象類:
package com.wk.cafe;
public abstract class Beverage {
String description = "Unkown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
調料抽象類,也就是裝飾者類:
package com.wk.cafe;
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
實現具體飲料:
package com.wk.cafe;
public class Espresso extends Beverage {
public Espresso(){
description = "Espresso";
}
public double cost(){
return 1.99;
}
}
package com.wk.cafe;
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "HouseBlend";
}
public double cost() {
return 0.89;
}
}
實現具體裝飾者類:
package com.wk.cafe;
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 0.20+beverage.cost();
}
}
package com.wk.cafe;
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 0.20+beverage.cost();
}
}
package com.wk.cafe;
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription()+",Mocha";
}
public double cost(){
return 0.20+beverage.cost();
}
}
測試程式碼:
package com.wk.cafe;
public class StartbuzzCoffee {
public static void main(String args[]) {
Beverage beverage1 = new Espresso();
System.out.println(beverage1.getDescription() + " $"
+ beverage1.cost());
beverage1 = new Mocha(beverage1);
beverage1 = new Whip(beverage1);
System.out.println(beverage1.getDescription() + " $"
+ beverage1.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類
Java I/O引出裝飾者模式的一個“缺點”:利用裝飾者模式,會造成設計中存在大量的小類。
我們可以編寫自己的Java I/O裝飾者(即ConcreteDecorator),它的作用是把輸入流中的所有小寫字母轉成大寫:
程式碼如下:
package com.wk.cafe;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UpperCaseInputStream extends FilterInputStream {
protected UpperCaseInputStream(InputStream in) {
super(in);
// TODO Auto-generated constructor stub
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase((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.toUpperCase((char) b[i]);
}
return result;
}
}
測試程式碼:
package com.wk.cafe;
import java.io.*;
public class UpperCaseInputStreamTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in = new UpperCaseInputStream(new BufferedInputStream(
new FileInputStream("E:\\inputtest.txt")));
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
執行結果: