Decorator 模式


您 打算設計一個點餐程式,目前主餐有炸雞、漢堡,您打算讓點了主餐的客入選擇附餐時可以有優惠,如果您使用繼承的方式來達到這個目的,例如:
class FriedChicken {
    double price() {
        return 49.0;
    }
}

class SideDishOne extends FriedChicken {
    double price() {
        return super.price() + 30.0;
    }
}

在使用繼承時,多想一下這個問題是否只能用繼承來解決總是好的。以這個設計為例,您繼承父類別之後,只是取得父類別的price()執行結果再進一步加以處理,另一方面,如果漢堡 也想要搭配附餐一,目前的SideDishOne顯然無法給漢堡重用, 您還得為漢堡建立有附餐一的子類別。
 
如果採取以下的設計,可以解決問題:


這個設計不採取繼承而改以組合的方式,如果以Java來示範如何為不同的主餐增加附餐的話,如下所示:
interface Meal {
String getContent();
double price();
}

class FriedChicken implements Meal {
public String getContent() {
return "不黑心炸雞";
}
public double price() {
return 49.0;
}
}

class Hamburger implements Meal {
public String getContent() {
return "美味蟹堡";
}
public double price() {
return 99.0;
}
}

abstract class SideDish implements Meal {
protected Meal meal;
SideDish(Meal meal) {
this.meal = meal;
}
}

class SideDishOne extends SideDish {
SideDishOne(Meal meal) {
super(meal);
}
public String getContent() {
return meal.getContent() + " | 可樂 | 薯條";
}
public double price() {
return meal.price() + 30.0;
}
}

public class Main {
public static void main(String[] args) {
Meal meal = new SideDishOne(new FriedChicken());
System.out.println("點了:" + meal.getContent());
System.out.println("價格:" + meal.price());
}
}

各種SideDish的實現並不改變Meal實作本來的操作功能,而是基於原本的操作功能再增加處理,SideDish的各種實現,可以套用至Meal的 各種實作,例如FriedChicken或Hambergur。

這是Decorator模式的實現,其不採取繼承的方式,而以組合的方式動態地為物 件添加功能。

以Python來實現的話:
class FriedChicken:
def getContent(self):
return "不黑心炸雞"
def price(self):
return 49.0

class Hamburger:
def getContent(self):
return "美味蟹堡"
def price(self):
return 99.0

class SideDish:
def __init__(self, meal):
self.meal = meal

class SideDishOne(SideDish):
def __init__(self, meal):
SideDish.__init__(self, meal)

def getContent(self):
return self.meal.getContent() + " | 可樂 | 薯條"

def price(self):
return self.meal.price() + 30.0

meal = SideDishOne(FriedChicken())
print("點了:" + meal.getContent())
print("價格:" + str(meal.price()))

以UML來表示Decorator模式之結構:



在Java Swing中的JTextArea元件預設並沒有捲軸,捲 軸的功能是由JScrollPane元件提供,如果您要加入一個具有捲軸功能的JTextArea,您可以如下進行設計:
JTextArea textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
 
像這樣動態地為JTextArea加入功能的方法,也是Decorator模式的實現,您不用修改JTextArea的功能,也不用使用繼承來擴充 JTextArea,對JTextArea來說,JScrollPane就好像是一個捲軸外框,直接套 在JTextArea上作裝飾,就好比您在照片上加上一個相框的意思。

在Gof的書中指出另一個範例,它設計一個Stream抽象類,而有一個StreamDecorator類,Stream的子類有處理記憶體串流的 MemoryStream與FileStream,有各種方法可以處理串流,也許只是單純的處理字元,也許會進行壓縮,也許會進行字元轉換,最基本的處理 可能是處理字元,而字元壓縮被視為額外的功能,這個時候我們可以使用裝飾模式,在需要的時候為Stream物件加上必要的功能,事實上在java.io中 的許多輸入輸出物件,就是採取這樣的設計,例如:
BufferedReader reader = new BufferedReader(new FileReader("Main.java"));

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

透過適當地設計,Decorator角色的類別,也可以重用於適當的元件。