1. 程式人生 > >Head First設計模式:(三)裝飾者模式

Head First設計模式:(三)裝飾者模式

星巴茲咖啡準備更新訂單系統,以合乎他們的飲料供應需求。

他們原先的類設計為:

這樣的訂單系統沒有辦法考慮到咖啡調料的部分,把加入不同調料的咖啡看做不同的類會導致類爆炸(每個類的cost方法計算出咖啡加調料的價錢):

很明顯,這樣的系統難以維護,一旦牛奶的價錢上揚或新增一種焦糖調料,系統將難以改變。

採用例項變數和繼承的設計也許能解決一些問題:

Beverage作為一個飲料類,加上例項變數代表是否加入了飲料。

然而當用戶想要雙倍摩卡咖啡時,這樣的系統就顯得有些無所適從。

對於冰茶,飲料基類裡的有些調料根本不適用,但是也一起繼承了過來!

到目前為止,使用繼承會造成的問題有:類爆炸,設計死板,以及基類加入的新功能並不適用於所有的子類。

所以繼承並不是解決問題的方法,應當使用組合來使系統更有彈性且易於維護。

開放-關閉原則:

設計原則:

類應該對擴充套件開放,對修改關閉。

我們的目標是允許類容易擴充套件,在不修改現有程式碼的情況下,就可搭配新的行為。

這個目標需要使用裝飾著模式實現:以飲料為主體,然後執行調料來“裝飾”飲料。

如圖為一個摩卡和奶泡DarkRoast咖啡的設計圖:

定義裝飾者模式:

裝飾者模式動態的將責任附加到物件上,若要擴充套件功能,裝飾者提供了比繼承更具有彈性的替代方案。

裝飾者模式類圖:

現在讓星巴茲咖啡系統也符合裝飾者類圖:

具體實現:

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

package com.cafe;

public abstract class Beverage {
	String description = "Unknow Beverage";

	public String getDescription() {
		return description;
	}

	public abstract double cost();
}

調料抽象類,也就是裝飾者類:

package com.cafe;

public abstract class CondimentDecorator extends Beverage{
   public abstract String getDescription();
}

實現具體的飲料(濃縮咖啡和綜合咖啡):

package com.cafe;

public class Espresso extends Beverage {
	public Espresso() {
		description = "Espresso";
	}

	public double cost() {
		return 1.99;
	}
}
package com.cafe;

public class HouseBlend extends Beverage {
	public HouseBlend() {
		description = "HouseBlend";
	}

	public double cost() {
		return 0.89;
	}
}

實現具體裝飾者類(摩卡)

package com.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.cafe;

public class StartbuzzCoffee {
	public static void main(String args[]) {
		Beverage beverage1 = new Espresso();
		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裝飾者,把輸入流中的所有大寫字母轉成小寫:

package com.io;

import java.io.*;

public class InputTest {
	public static void main(String[] args) throws IOException {
		int c;

		try {
			InputStream in = new LowerCaseInputStream(new BufferedInputStream(
					new FileInputStream("D:\\test.txt")));

			while ((c = in.read()) >= 0) {
				System.out.print((char) c);
			}

			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

測試程式(測試剛剛寫好的I/O裝飾者)

package com.io;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LowerCaseInputStream extends FilterInputStream {

	protected LowerCaseInputStream(InputStream in) {
		super(in);
		// TODO Auto-generated constructor stub
	}

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

測試結果: