設計模式學習(五) 裝飾者模式
引入
本節可以稱為 “給愛用繼承的人一個全新的設計眼界”。
我們即將再度討論典型的繼承濫用問題,在本章學到如何使用物件組合的方式,做到執行時裝飾類,一旦你熟悉了裝飾的技巧,你將能夠在不修改任何底層程式碼的情況下,給你的物件賦予新的職責。
開放-關閉原則:類應該對擴充套件開放,對修改關閉
引用head first
定義:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。
UML類圖
裝飾者模式共有四大角色:
- Component:抽象元件
- 可以是一個介面或者是抽象類,其充當的就是被裝飾的原始物件,用來定義裝飾者和被裝飾者的基本行為。
- ConcreteComponent:元件具體實現類
- 該類是 Component 類的基本實現,也是我們裝飾的具體物件。
- Decorator:抽象裝飾者
- 裝飾元件物件,其內部一定要有一個指向元件物件的引用。在大多數情況下,該類為抽象類,需要根據不同的裝飾邏輯實現不同的具體子類。當然,如果是裝飾邏輯單一,只有一個的情況下我們可以忽略該類直接作為具體的裝飾者。
- ConcreteDecoratorA 和 ConcreteDecoratorB:裝飾者具體實現類
- 對抽象裝飾者的具體實現。
在已有的 Component 和 ConcreteComponent 體系下,實現裝飾者模式來擴充套件原有系統的功能,可以分為 5 個步驟
- 繼承或者實現 Component 元件,生成一個 Decorator 裝飾者抽象類;
- 在生成的這個 Decorator 裝飾者類中,增加一個 Component 的私有成員物件;
- 將 ConcreteComponent 或者其他需要被裝飾的物件傳入 Decorator 類中並賦值給上一步的 Component 物件;
- 在裝飾者 Decorator 類中,將所有的操作都替換成該 Component 物件的對應操作;
- 在 ConcreteDecorator 類中,根據需要對應覆蓋需要重寫的方法。
示例
引用head first 星巴克咖啡例子
package com.zpkj.project8; /** * Component * 裝飾者模式 頂層 */ public abstract class Beverage { protected String description; public abstract double cost();
public String getDescription() { return description; } }
package com.zpkj.project8;
//被裝飾物件 public class DarkRoast extends Beverage{ public DarkRoast() { description = "深焙咖啡"; } @Override public double cost() { return 24; }
}
package com.zpkj.project8; //被裝飾物件 public class Decaf extends Beverage{
public Decaf() { description = "無咖啡因咖啡"; }
@Override public double cost() { return 36; }
}
package com.zpkj.project8; //被裝飾物件 public class Espresso extends Beverage{ public Espresso() { description ="濃縮咖啡"; }
@Override public double cost() { return 29; }
}
package com.zpkj.project8; //被裝飾物件 public class HouseBlend extends Beverage{ public HouseBlend() { description = "黑咖啡"; }
@Override public double cost() { return 28; }
}
package com.zpkj.project8;
/** * 裝飾者共同實現的介面(也可以是抽象類) */ public abstract class CondimentDecorator extends Beverage{ protected Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } public abstract String getDescription(); }
package com.zpkj.project8; //裝飾者具體實現 public class Whip extends CondimentDecorator{ public Whip(Beverage beverage) { super(beverage); }
@Override public String getDescription() { return beverage.getDescription()+",奶泡"; }
@Override public double cost() { return beverage.cost()+2; }
}
package com.zpkj.project8; //裝飾者具體實現 public class Mocha extends CondimentDecorator{ public Mocha(Beverage beverage) { super(beverage); }
@Override public String getDescription() { return beverage.getDescription()+",摩卡"; }
@Override public double cost() { return beverage.cost()+5; }
}
測試
Java 中的裝飾者模式
我們從java i/o也引出裝飾者模式的一個缺點,利用裝飾者模式,常常造成設計中有大量的小類,造成api使用的困擾,當了解裝飾者模式的工作原理,很容易就能知道類結構是如何組織的。
編寫自己的i/o裝飾者
編寫一個裝飾者,把輸入流內的所有大寫字元轉小寫
package com.zpkj.project8;
import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream;
public class LowerCaseInputStream extends FilterInputStream{
public LowerCaseInputStream(InputStream in) { super(in); }
@Override public int read() throws IOException { int c = super.read(); return (c == -1 ? c : Character.toLowerCase((char)c)); }
@Override public int read(byte[] b) throws IOException { return super.read(b); }
@Override public int read(byte[] b, int off, int len) throws IOException { int result = super.read(b, off, len); for(int i = off;i<result+off;i++){ b[i]=(byte)Character.toLowerCase((char)b[i]); } return result; }
}
package com.zpkj.project8;
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;
public class FileTest { public static void main(String[] args) throws IOException { int c; InputStream inputStream = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream("G:"+File.separator+"test.txt"))); while((c = inputStream.read()) >=0){ System.out.print((char)c); } inputStream.close(); }
}
結果:
總結:
動態地將責任附加到物件上。想要擴充套件功能,裝飾者提供有利於繼承的另一種選擇。
1.繼承屬於擴充套件形式之一,但不見得是達到彈性設計的最佳方案
2.在設計中,應允許行為可以被擴充套件,而無需修改現有的程式碼
3.組合和委任可以用在執行時動態加上新行為
4.除了繼承、裝飾者也可以讓我們擴充套件行為
5.裝飾者模式意味著一群裝飾者類,這些類用來包裝具體元件
6.裝飾者反映出被裝飾的元件型別,(事實上,他們都具有相同的型別,都經過介面或繼承實現)。
7.裝飾者可以在被裝飾者的行為前面或後面加上自己的行為,甚至將裝飾者的行為取代掉,達到特定目的
8.可以用無數裝飾者包裝一個元件
9.裝飾者一般對元件的客戶是透明的,除非客戶程式依賴於元件的具體型別
10.裝飾者會導致設計中出現許多小物件,如果過度使用,會讓程式變得很複雜