1. 程式人生 > >Header First設計模式——裝飾者模式

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)計算出價錢,然後再加上奶泡的價錢。到此。我們知道了:

1.裝飾者和被裝飾物件有相同的超型別。 2.你可以用一個或多個裝飾者包裝一個物件。 3.幾人裝飾者和被裝飾者物件有相同的超型別,所有在任何需要原始物件(被包裝的)的場合,可以用裝飾過的物件代替它。 4.裝飾者可以在所委託被裝飾者的行為之前與/或之後,加上自己的行為,以達到特定的目的。(關鍵點) 5.物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量地用你喜歡的裝飾者來裝飾物件。定義裝飾者模式:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。 看看它的類圖:

裝飾者類圖

裝飾者我們的飲料 讓星巴茲飲料也能符合此框架星巴茲裝飾者類圖

具體實現:

從飲料下手,將飲料作為抽象類:

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();  
        }  
    }  
} 

執行結果: