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;
}
}
測試結果: