2010年11月4日 星期四

[OO 設計模式] Decorator Pattern : 裝飾模式 - 動態增加類別功能

轉載自 這裡 
前言 : 
您打算設計一個點餐程式,目前主餐有炸雞、漢堡,您打算讓點了主餐的客入選擇附餐時可以有優惠,如果您使用繼承的方式來達到這個目的,例如: 

  1. class FriedChicken {  
  2.     double price() {  
  3.         return 49.0;  
  4.     }  
  5. }  
  6.   
  7. class SideDishOne extends FriedChicken {  
  8.     double price() {  
  9.         return super.price() + 30.0;  
  10.     }  
  11. }  
在使用繼承時,多想一下這個問題是否只能用繼承來解決總是好的。以這個設計為例,您繼承父類別之後,只是取得父類別的price()執行結果再進一步加以處理,另一方面,如果漢堡也想要搭配附餐一,目前的SideDishOne顯然無法給漢堡重用,您還得為漢堡建立有附餐一的子類別。 

建議解法 : 
如果採取以下的設計,可以解決問題: 
 
這個設計不採取繼承而改以組合的方式,如果以Java來示範如何為不同的主餐增加附餐的話,如下所示: 
* Meal 介面代碼 : 

  1. package ch06.order.proto;  
  2.   
  3. public interface Meal {  
  4.     public String getContent();  
  5.     public double getPrice();  
  6. }  
* 類別AbstractSideDish代碼 : 

  1. package ch06.order.proto;  
  2.   
  3. public abstract class AbstractSideDish implements Meal{  
  4.     protected Meal meal;  
  5.     public AbstractSideDish(Meal m){  
  6.         this.meal = m;  
  7.     }  
  8. }  
* FriedChicken 類別代碼 : 

  1. package ch06.order.food;  
  2.   
  3. import ch06.order.proto.*;  
  4.   
  5. public class FriedChicken implements Meal{  
  6.     private String content;  
  7.     private double price;  
  8.       
  9.     public FriedChicken(String name, double price){  
  10.         this.content = name;  
  11.         this.price = price;  
  12.     }  
  13.   
  14.     @Override  
  15.     public String getContent() {          
  16.         return content;  
  17.     }  
  18.   
  19.     @Override  
  20.     public double getPrice() {  
  21.         return price;  
  22.     }  
  23. }  
* SideDishOne 類別代碼 : 

  1. package ch06.order.food;  
  2.   
  3. import ch06.order.proto.AbstractSideDish;  
  4. import ch06.order.proto.Meal;  
  5.   
  6. public class SideDishOne extends AbstractSideDish{  
  7.       
  8.     public SideDishOne(Meal m){  
  9.         super(m);  
  10.     }  
  11.   
  12.     @Override  
  13.     public String getContent() {          
  14.         return meal.getContent()+" | 可樂 | 薯條";  
  15.     }  
  16.   
  17.     @Override  
  18.     public double getPrice() {        
  19.         return meal.getPrice() + 30;  
  20.     }  
  21. }  
* 主程式 Main 類別代碼 : 

  1. package ch06.order;  
  2.   
  3. import ch06.order.food.*;  
  4. import ch06.order.proto.*;  
  5.   
  6. public class Main {  
  7.     public static void main(String args[]) {  
  8.         Meal sideDishMeal = new SideDishOne(new FriedChicken("好吃炸雞"100));  
  9.         System.out.println("Content: " + sideDishMeal.getContent());  
  10.         System.out.println("Price  : " + sideDishMeal.getPrice());  
  11.     }  
  12. }  
各種SideDish的實現並不改變Meal實作本來的操作功能,而是基於原本的操作功能再增加處理,SideDish的各種實現,可以套用至Meal的各種實作,例如FriedChicken或Hambergur。 
這是Decorator模式的實現,其不採取繼承的方式,而以組合的方式動態地為物件添加功能 

執行結果 : 

Content: 好吃炸雞 | 可樂 | 薯條
Price : 130.0


Decorator 設計模式介紹 : 
通過使用修飾模式,可以在運行時擴充一個類的功能。原理是:增加一個修飾類包裹原來的類,包裹的方式一般是通過在將原來的對象作為修飾類的構造函數的參數。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接調用原來的類中的方法。修飾類必須和原來的類有相同的介面。 
修飾模式是類繼承的另外一種選擇。類繼承在編譯時候增加行為,而裝飾模式是在運行時增加行為。 
當有幾個相互獨立的功能需要擴充時,這個區別就變得很重要。在有些物件導向的程式語言中,類不能在運行時被創建,通常在設計的時候也不能預測到有哪幾種功能組合。這就意味著要為每一種組合創建一個新類。相反,修飾模式是面向運行時候的對象實例的,這樣就可以在運行時根據需要進行組合。一個修飾模式的示例是JAVA里的Java I/O Streams的實現。 
底下以UML來表示Decorator模式之結構: 
 

在Java Swing中的JTextArea元件預設並沒有捲軸,捲軸的功能是由JScrollPane元件提供,如果您要加入一個具有捲軸功能的JTextArea,您可以如下進行設計: 

  1. JTextArea textArea = new JTextArea();  
  2. JScrollPane scrollPane = new JScrollPane(textArea);  
像這樣動態地為JTextArea加入功能的方法,也是Decorator模式的實現,您不用修改JTextArea的功能,也不用使用繼承來擴充JTextArea,對JTextArea來說,JScrollPane就好像是一個捲軸外框,直接套在JTextArea上作裝飾,就好比您在照片上加上一個相框的意思。 
在Gof的書中指出另一個範例,它設計一個Stream抽象類,而有一個StreamDecorator類,Stream的子類有處理記憶體串流的 MemoryStream與FileStream,有各種方法可以處理串流,也許只是單純的處理字元,也許會進行壓縮,也許會進行字元轉換,最基本的處理可能是處理字元,而字元壓縮被視為額外的功能,這個時候我們可以使用裝飾模式,在需要的時候為Stream物件加上必要的功能,事實上在java.io中的許多輸入輸出物件,就是採取這樣的設計,例如: 

  1. BufferedReader reader = new BufferedReader(new FileReader("Main.java"));  

FileReader 沒有緩衝區處理的功能,所以由BufferedReader來提供,BufferedReader並沒有改變FileReader的功能,而是在既有 FileReader的操作上再作加工的動作,而BufferedReader也不只可以用於FileReader,只要是Reader的子類別,都可以套用BufferedReader,例如讀取使用者輸入時: 

  1. BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
透過適當地設計,Decorator角色的類別,也可以重用於適當的元件。 

補充說明 : 
* Wiki Decorator 修飾模式 
* 史帝芬心得筆記-- Decorator Pattern (裝飾模式)

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...