begin 2018年9月29日14:19:22
裝飾者模式(Decorator Pattern)
定義
金玉其外,敗絮其中。
動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾者模式比生成子類更為靈活。——《設計模式:可復用面向對象軟件的基礎》
裝飾者模式是一種對象結構型模式。
使用場景
-
在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
-
需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。
-
當不能采用繼承的方式對系統進行擴充或者采用繼承不利於系統擴展和維護時。不能采用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,為支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類).
角色
抽象構件角色(Component):定義一個對象接口或抽象類,可以給這些對象動態地添加職責。
具體構件角色(ConcreteComponent):實際被動態地添加職責的對象。
抽象裝飾者角色(Decorator):實現了Component接口,用來擴展Component類的功能,但對於Component來說,是無需知道Decorator的存在的。
具體裝飾者角色(ConcreteDecorator):動態地添加職責的對象。
圖示
裝飾者模式(Decorator)結構圖
代碼示例
裝飾者模式(Decorator)代碼示例類圖
這是一個關於相親的故事。某家有女初長成,七大姑八大姨欲幫忙覓得佳婿,后在各種群、圈、網站,收集了各種男人的資料。這里需要一個應用生成收集過來的男人的描述,如:是否有車、是否有房、是否有存款、是否有好品質。然后這個應用了裝飾者模式的應用就誕生了,動態添加男人的描述,最后得到男人的綜述。
抽象構件角色(Man.java):
// 男人
public interface Man {
public void getManDesc();
}
具體構件角色(NormalMan.java):
// 普通男人
public class NormalMan implements Man{
private String name = null;
public NormalMan(String name) {
this.name = name;
}
@Override
public void getManDesc() {
System.out.print(name + ": ");
}
}
抽象裝飾者角色(AttachedPropertiesDecorator.java):
// 附加屬性裝飾者
public abstract class AttachedPropertiesDecorator implements Man{
private Man man;
public AttachedPropertiesDecorator(Man man) {
this.man = man;
}
public void getManDesc() {
man.getManDesc();
}
}
具體裝飾者角色(CarDecoratorImpl.java、HouseDecoratorImpl.java、DepositDecoratorImpl.java、QualityDecoratorImpl.java):
// 小車裝飾者
public class CarDecoratorImpl extends AttachedPropertiesDecorator{
private String car = "有車";
public CarDecoratorImpl(Man man) {
super(man);
}
public void addCar() {
System.out.print(car + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addCar();
}
}
// 房子裝飾者
public class HouseDecoratorImpl extends AttachedPropertiesDecorator{
private String house = "有房";
public HouseDecoratorImpl(Man man) {
super(man);
}
public void addHouse() {
System.out.print(house + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addHouse();
}
}
// 存款裝飾者
public class DepositDecoratorImpl extends AttachedPropertiesDecorator{
private String deposit = "有存款";
public DepositDecoratorImpl(Man man) {
super(man);
}
public void addDeposit() {
System.out.print(deposit + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addDeposit();
}
}
// 品質裝飾者
public class QualityDecoratorImpl extends AttachedPropertiesDecorator{
private String quality = "有好品質";
public QualityDecoratorImpl(Man man) {
super(man);
}
public void addQuality() {
System.out.print(quality + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
}
}
有車、有房、有存款、有品質具體修飾者。
裝飾者模式測試類(DecoratorTest.java):
public class DecoratorTest {
public static void main(String[] args) {
Man man = new NormalMan("張三");
Man man1 = new CarDecoratorImpl(man);
Man man2 = new HouseDecoratorImpl(man1);
Man man3 = new DepositDecoratorImpl(man2);
System.out.println("層層裝飾:");
man3.getManDesc();
System.out.println();
System.out.println("重復裝飾(有兩個'有存款'):");
Man man4 = new DepositDecoratorImpl(man3);
man4.getManDesc();
System.out.println();
System.out.println("任意修飾:");
Man man5 = new QualityDecoratorImpl(man1);
man5.getManDesc();
System.out.println();
System.out.println("直接得到修飾結果:");
Man man6 = new HouseDecoratorImpl(new DepositDecoratorImpl(new NormalMan("李四")));
man6.getManDesc();
System.out.println();
}
}
運行結果:
優點
1、裝飾者模式和繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活型。
2、通過使用不同的具體裝飾者類及它們不同的組合順序,可以得到不同裝飾后具有不同行為或者狀態的對象。例如上面的CarDecoratorImpl可以多次修飾一個男人,證明他有很多車。
3、符合開閉原則。
缺點
1、增加了抽象裝飾者類和具體裝飾者類,一定程度增加了系統的復雜度,加大了系統的學習和理解成本。
2、靈活性也意味着更容易出錯,對於多次被多次修飾的對象,調試時尋找錯誤可能需要找到多個地方。
裝飾者模式進階
透明與半透明的裝飾者模式
透明的裝飾者模式:要求客戶端完全針對抽象編程(依賴倒置原則),裝飾者模式的透明型要求客戶端程序不應該聲明具體構件類型和具體裝飾者類型,而應該全部聲明為抽象構件類型。當然調用的也是抽象構件類聲明的接口方法了。
Man man = new NormalMan("張三");
Man man1 = new CarDecoratorImpl(man);
Man man2 = new HouseDecoratorImpl(man1);
Man man3 = new DepositDecoratorImpl(man2);
半透明的裝飾者模式:允許用戶在客戶端聲明具體裝飾者類型的對象,允許在具體裝飾者中新增方法且客戶端可以調用這些新增的方法。
Man man = new NormalMan("張三");
CarDecoratorImpl man1 = new CarDecoratorImpl(man);
HouseDecoratorImpl man2 = new HouseDecoratorImpl(man);
DepositDecoratorImpl man3 = new DepositDecoratorImpl(man);
man1.addCar();
man2.addHouse();
man3.addDeposit();
java.io中裝飾者模式的應用
java I/O類繼承結構圖:
雖然java I/O中類比較多,但是裝飾者模式的應用方式是差不多的。下面舉一個例子:InputStream及其子類
裝飾者模式對照上圖可得:
抽象構件角色:InputStream,這是一個抽象類。
具體構件角色:ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream(已棄用)、AudioInputStream等,實現了InputStream抽象類。
抽象裝飾者角色:FilterInputStream,實現了抽象構件角色,且成為具體裝飾者的父類。
具體裝飾者模式:FilterInputStream的子類如常用的BufferedInputStream、DataInputStream,還有其他不常用的CheckedInputStream、CipherInputStream、LineNumberInputStream、PushBackInputStream。這里其實具體的裝飾者還有直接繼承InputStream的ObjectInputStream、SequenceInputStream,這里涉及到下面介紹的裝飾者模式的簡化。
這里的裝飾者模式大多都是半透明的,具體裝飾者提供了比抽象構件多的方法,客戶端可以使用這些方法。
裝飾者模式的簡化
裝飾者有3種簡化模式:
第一種情況,只有一個具體裝飾者,這樣就不需要抽象裝飾者,具體裝飾者直接繼承抽象構件就可以了。
第二種情況,只有一個具體構件,這樣就不需要抽象構件,抽象裝飾者可以直接繼承具體構件就可以了。
第三種情況,就是上面組合起來:只有一個具體構件和一個具體裝飾者,這樣抽象角色都不需要了,具體裝飾者直接繼承集體構件就可以了。
總結
需要動態給某個對象添加職責,使用裝飾者模式。
完
end 2018年10月16日15:24:03