場景描述:咖啡店的訂單系統,初始的類設計如下
現提供加入配料的服務,在原有的系統上進行擴展。
初次嘗試:直接爲每個組合創建一個類,帶來的問題就是類的數量過多,且毫無彈性可言。
再次嘗試:從基類Beverage下手,通過實例變量表示是否加上調料。
帶來的問題:如果發生需求變動,比如調整調料價格,加入新的調料等等都會造成對原有代碼的修改。也沒有實現彈性設計。
設計原則:類應該對擴展開放,對修改關閉。
我們的目標是允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行爲。
上述原則叫做“開放-關閉”原則,遵循開放-關閉原則,通常會引入新的抽象層次,增加代碼的複雜度。
每個地方都採用開放-關閉原則,是一種浪費,也沒必要,還會導致代碼變得複雜且難以理解。
我們看一下裝飾者模式,使用它來解決我們的問題。
裝飾者模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性地替代方案。
裝飾者和被裝飾對象有相同地超類型。
你可以用一個或多個裝飾者包裝一個對象。
裝飾者可以在所委託被裝飾者地行爲之前與/或之後,加上自己的行爲,以達到特定的目的。
那麼試着通過裝飾者模式解決我們最開始的問題。
那麼我們看一下相關的代碼實現。
首先是基類Beverage
public abstract class Beverage {
public String description = "Unknown starbuzz.Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
然後看一下裝飾者類
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
然後我們實現一些飲料
public class Espresso extends Beverage {
public Espresso(){
description = "starbuzz.coffee.Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
public class HouseBlend extends Beverage {
public HouseBlend(){
description = "House Blend Coffee";
}
@Override
public double cost() {
return .89;
}
}
接着實現調料類
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + ", starbuzz.condiment.Mocha";
}
@Override
public double cost() {
return .20 + beverage.cost();
}
}
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + ", starbuzz.condiment.Soy";
}
@Override
public double cost() {
return .15 + beverage.cost();
}
}
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
public String getDescription(){
return beverage.getDescription() + ", starbuzz.condiment.Whip";
}
@Override
public double cost() {
return .10 + beverage.cost();
}
}
然後我們測試一下
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.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包中大量使用了裝飾者模式。如
所以我們也可以嘗試自己實現一個io裝飾者來包裝io包中的類。它嘗試把輸入的字節流中的所有大寫都轉換爲小寫。
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(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 InputTest {
// 相對於整個項目的文件路徑
private static String file = "/io_extend/test.txt";
public static void main(String[] args) {
int c;
try {
// System.out.println(new File(".").getAbsolutePath());
//當前類的絕對路徑
// System.out.println(InputTest.class.getResource("/").getFile());
//指定CLASSPATH文件的絕對路徑
// System.out.println(InputTest.class.getResource(file).getFile());
InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream(InputTest.class.getResource(file).getFile())));
while ((c = in.read()) >= 0){
System.out.print((char) c);
}
in.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
總結:裝飾者模式的使用場景爲,當我們需要在不改動原有代碼的情況下,能夠動態的添加新的功能(行爲)時,就可以使用裝飾者;尤其是java.io包給了我們一個很好的參照,同時也暴露出它的問題,就是會導致類變得特別的多,因爲每新增一個行爲或功能時,都需要進行一次包裝。