設計模式——03 裝飾者模式
3 Decorator Pattern(裝飾者模式)
3.1設計原則一
類應該對擴充套件開放,對修改關閉
前言:裝飾者模式主要是為了解決繼承濫用的問題,以下將使用物件組合的方式做到在執行時裝飾類。
1)案例分析一:
REQ1:星巴克咖啡店咖啡種類擴充套件飛快,Vander作為其老闆,準備儘快更新訂單系統來滿足這一發展。原先的設計如下:
分析:隨著飲品的發展,每種飲料都可以自由搭配,而且本身飲料也很多,除了咖啡之外,鴛鴦、奶茶、可樂、雪碧、酸奶、豆漿等等,而且調料還可以自由選擇。使用不同的調料需要付不同的價格,例如一小份牛奶加入咖啡中,加收1塊錢等等。如果繼續按照上述的設計繼續,則繼承Beverage抽象類的飲料將非常多,例如coffeewithonemilk,milkTeaWithSteamAndSoy等等,這樣能產生無數的搭配,並且牛奶價格上升之後,每個涉及到牛奶的類的cost函式還需要修改,這簡直就是噩夢。
解決方法1:Vander 就開始設計了
超類cost()將計算所有調料的價格,子類覆蓋的cost()方法擴充套件超類的功能,把指定的飲料型別的價錢也加上。
public class Beverage { private String desc; private boolean milk; private boolean soy; private boolean mocha; private boolean whip; public boolean hasMilk() { return this.milk; } public boolean hasSoy() { return this.soy; } public boolean hasMocha() { return this.mocha; } public boolean hasWhip() { return this.whip; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public void setMilk(boolean milk) { this.milk = milk; } public void setSoy(boolean soy) { this.soy = soy; } public void setMocha(boolean mocha) { this.mocha = mocha; } public void setWhip(boolean whip) { this.whip = whip; } public float cost() { float flavourCost = 0.0f; if(hasMilk()) { flavourCost = flavourCost + 1.0f; } if(hasSoy()) { flavourCost = flavourCost + 2.0f; } if(hasMocha()) { flavourCost = flavourCost + 3.0f; } if(hasWhip()) { flavourCost = flavourCost + 4.0f; } return flavourCost; } } public class DarkRoast extends Beverage { private float cost; public float getCost() { return cost + super.cost(); } public void setCost(float cost) { this.cost = cost; } }
存在的問題:
這個方法出現了以下4個問題:
1、當調料價格改變的時候需要修改Beverage類的程式碼。
2、一旦有新的調料,需要加上新的方法,並改變超類中的cost()方法。
3、如果有新的飲料(Tea),對這些飲料而言,某些調料(如soy、stream等)可能並不適合,但是這個設計方式中,Tea子類仍將繼承那些不合適的方法,例如:hasSoy()(加入豆漿)
4、顧客萬一不只是要一份摩卡,想加入兩份摩卡調料,上述的方法根本無法應對。
解決方法3(使用裝飾者模式):
REQ2:首先有這麼一種需求,顧客買了一杯DarkRoast,想加Mocha,然後再加奶泡Whip,最後要計算這杯DarkRoast的金額。
裝飾者模式可以用下面的圖來說明,該圖上可以看到Whip包裹著Mocha,而Mocha又包裹了DarkRoast,並且這三個類的基類都是Beverage,DarkRoast繼承自Beverage,且有一個用來計算費用的cost()方法,Mocha物件是一個裝飾者,它的型別反映了它所裝飾的物件;Whip也是一個裝飾者,所以它也反映了DarkRoast型別,幷包括一個cost()方法。
最後到結賬的時候,先呼叫最外層的Whip,得到了Whip的價格,然後再呼叫Mocha的cost,此時Whip的價格傳給了Mocha,這樣Mocha再加上自己的價格,現在就得到了Mocha+Whip的價格,然後再呼叫DarkRoast的價格,最後就得到了這杯咖啡的價格。這樣相當於就做到“在執行時決定類的行為”。
這裡要說明的是,Beverage可以用介面也可以用抽象類,若需要加入屬性的話,就使用抽象類,若不需要屬性則可以使用介面,方便日後程式碼可以extends其他的類。
以下是關鍵程式碼:
Beverage beverage = new DarkRoast();
beverage = new Whip(beverage);
beverage = new Mocha(beverage);
public class Mocha implements CondimentDecorator {
private Beverage beverage;
public float cost() {
return this.beverage.cost() + 1.0f;
}
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDesc() {
return this.beverage.getDesc() + " with " + "Mocha";
}
}
REQ3:那麼問題來了,現實的Java世界中有哪些用了裝飾者模式呢,你是否看過這樣的一句new LineNumberInputStream(new BufferedInputStream(new FileInputStream)),這句話看似很複雜,其實這就是典型的裝飾者模式,FileInputStream是被裝飾的“元件”,Java I/O程式庫提供了幾個元件,包括了FileInputStream、StringBufferInputStream、ByteArrayInputStream… …等。這些類都提供了最基本的位元組讀取功能。
BfferedInputStream是具體的裝飾者,加入了兩種行為,利用快取輸入來改進效能,用readline()方法(用來一次讀取一行文字輸入資料)來增強介面。
LineNumberInputStream也是一個具體的裝飾者,它加上了計算行數的功能。
下面進行一個小練習,寫一個IO裝飾者來講輸入流中所有的大寫字母轉成小寫。
public class LowcaseInputStream extends FilterInputStream {
public LowcaseInputStream(InputStream in) {
super(in);
}
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;
}
}
public class Main {
public static void main(String[] args) {
InputStream inputStream;
int c;
try {
inputStream = new FileInputStream("test.txt");
inputStream = new BufferedInputStream(inputStream);
inputStream = new LowcaseInputStream(inputStream);
while((c = inputStream.read()) >= 0) {
System.out.print((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
裝飾者模式的缺點:利用裝飾者模式,常常造成設計中有大量的小類,數量實在太多,可能造成使用此API程式設計師的困擾。但是其實瞭解了裝飾者的原理,就可以容易地辨別出他們的裝飾者類是如何組織的,以方便用包裝方式取得想要的功能。
面向物件基礎
抽象、封裝、多型、繼承
四大原則
設計原則一:封裝變化
設計原則二:針對介面程式設計,不針對實現程式設計。
設計原則三:多用組合,少用繼承。
設計原則四:為互動物件之間的鬆耦合設計而努力
設計原則五:對擴充套件開放,對修改關閉
模式
裝飾者模式:動態地將責任附加到物件上。想要擴充套件功能,裝飾者提供有別於繼承的另一種選擇。
最後獻上此次設計的原始碼,原始碼中包括了一些起初錯誤的實現,以及後期經過思考後正確的實現,在此處出現的所有原始碼均有實現,有需要的小夥伴可以下載來執行一下,首先先自己進行設計,然後再參考,這樣才能加深觀察者模式的理解。