1. 程式人生 > >設計模式學習(五) 裝飾者模式

設計模式學習(五) 裝飾者模式

引入

本節可以稱為  “給愛用繼承的人一個全新的設計眼界”。

我們即將再度討論典型的繼承濫用問題,在本章學到如何使用物件組合的方式,做到執行時裝飾類,一旦你熟悉了裝飾的技巧,你將能夠在不修改任何底層程式碼的情況下,給你的物件賦予新的職責。

開放-關閉原則:類應該對擴充套件開放,對修改關閉

引用head first

定義:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。

UML類圖

裝飾者模式共有四大角色:

  • Component:抽象元件
  • 可以是一個介面或者是抽象類,其充當的就是被裝飾的原始物件,用來定義裝飾者和被裝飾者的基本行為。
  • ConcreteComponent:元件具體實現類
  • 該類是 Component 類的基本實現,也是我們裝飾的具體物件。
  • Decorator:抽象裝飾者
  • 裝飾元件物件,其內部一定要有一個指向元件物件的引用。在大多數情況下,該類為抽象類,需要根據不同的裝飾邏輯實現不同的具體子類。當然,如果是裝飾邏輯單一,只有一個的情況下我們可以忽略該類直接作為具體的裝飾者。
  • ConcreteDecoratorA 和 ConcreteDecoratorB:裝飾者具體實現類
  • 對抽象裝飾者的具體實現。

  在已有的 Component 和 ConcreteComponent 體系下,實現裝飾者模式來擴充套件原有系統的功能,可以分為 5 個步驟

  1. 繼承或者實現 Component 元件,生成一個 Decorator 裝飾者抽象類;
  2. 在生成的這個 Decorator 裝飾者類中,增加一個 Component 的私有成員物件;
  3. 將 ConcreteComponent 或者其他需要被裝飾的物件傳入 Decorator 類中並賦值給上一步的 Component 物件;
  4. 在裝飾者 Decorator 類中,將所有的操作都替換成該 Component 物件的對應操作;
  5. 在 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.裝飾者會導致設計中出現許多小物件,如果過度使用,會讓程式變得很複雜