Decorator Pattern (裝飾模式)
裝飾模式可「動態」地給一個對象添加一些額外的職責提供有別於「繼承」的另一種選擇就擴展功能而言Decorator Pattern 透過 Aggregation (聚合) 的特殊應用降低了類與類之間的耦合度會比單獨使用「繼承」生成子類更為靈活
一般用「繼承」來設計子類的做法會讓程序變得較僵硬其對象的行為是在「編譯」時期就已經「靜態」決定的而且所有的子類都會繼承到相同的行為然而若用「裝飾模式」以及 UML 的 Aggregation 的設計來擴展對象的行為就能彈性地 (flexible) 將多個「裝飾者」混合著搭配使用而且是在「執行」時期「動態」地進行擴展
此外若用一般「繼承」的做法每當對象需要新行為時必須修改既有的代碼重新編譯但若透過「裝飾模式」則無須修改既有代碼
The Decorator Pattern attaches additional responsibilities to an object dynamically Decorators provide a flexible alternative to subclassing for extending functionality
- Design Patterns: Elements of Reusable ObjectOriented Software
圖 此圖為 Decorator 模式的經典 Class Diagram
_Shell / Programcs
using System;
namespace __Shell
{
//客戶端程序
class Program
{
static void Main(string[] args)
{
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d = new ConcreteDecoratorA();
ConcreteDecoratorB d = new ConcreteDecoratorB();
//讓 ConcreteDecorator 在運行時動態地給 ConcreteComponent 增加職責
dSetComponent(c);
dSetComponent(d);
dOperation();
ConsoleRead();
}
}
//裝飾者被裝飾者的共同基類
abstract class Component
{
public abstract void Operation();
}
//被裝飾者具體被裝飾對象
//此為在「執行」時期會被 ConcreteDecorator 具體裝飾者「動態」添加新行為(職責)的對象
class ConcreteComponent : Component
{
public override void Operation()
{
ConsoleWriteLine(具體被裝飾對象的操作);
}
}
//裝飾者
//此為所有裝飾者應共同實現的抽象類或接口
abstract class Decorator : Component
{
//以父類聲明的字段
//實現 UML Class Diagram 的 Aggregation指向 Component 對象的指針
protected Component component;
public void SetComponent(Component component)
{
ponent = component;
}
public override void Operation()
{
if (component != null)
{
componentOperation();
}
}
}
//具體裝飾者 A
//此為在「執行」時期替 ConcreteComponent「動態」添加新行為(職責)的對象
class ConcreteDecoratorA : Decorator
{
//裝飾者可以添加新的欄位
private string addedState;
public override void Operation()
{
baseOperation();
addedState = New State;
ConsoleWriteLine(具體裝飾對象 A 的操作);
}
}
//具體裝飾者 B
//此為在「執行」時期替 ConcreteComponent「動態」添加新行為(職責)的對象
class ConcreteDecoratorB : Decorator
{
public override void Operation()
{
baseOperation();
AddedBehavior();
ConsoleWriteLine(具體裝飾對象 B 的操作);
}
//裝飾者可以添加新的方法
private void AddedBehavior()
{
}
}
} // end of namespace
/*
執行結果
具體被裝飾對象的操作
具體裝飾對象 A 的操作
具體裝飾對象 B 的操作
*/
上方圖 的 Class Diagram以及「Shell (殼)」示例中ConcreteComponent 即為此一模式中的「被裝飾者」而 Decorator 抽象類及其具體子類 ConcreteDecoratorAConcreteDecoratorB 即為「裝飾者」此外這個模式中最核心的就是 Decorator 這個抽象類它用一個以父類 Component 聲明的變量 component實現 UML 中的 Aggregation (聚合有的博文也統稱為「組合」或「合成」)亦即為指向 Component 對象的指針達到我們前述 「動態」進行擴展的目的
至於其設計的原理以下我引用博客園一位前輩 Justin 兩年前所寫博文的一段內容 []這段是我認為對 Decorator 模式極優的說明讀者亦可搭配參考上方代碼裡我添加的注釋稍後我會再補充說明
Decorator 是裝飾者模式裡非常特殊的一個類它既繼承於 Component【IS A關系】又維護一個指向 Component 實例的引用【HAS A關系】換個角度來說Decorator 跟 Component 之間既有動態組合關系又有靜態繼承關系WHY? 這裡為什麼要這麼來設計?上面我們說過組合的好處是可以在運行時給對象增加職責Decorator【HAS A】Component 的目的是讓 ConcreteDecorator 可以在運行時動態地給 ConcreteComponent 增加職責這一點相對來說還比較好理解那麼 Decorator 繼承於 Component 的目的是什麼?在這裡繼承的目的只有一個那就是可以統一「裝飾者」和「被裝飾者」的接口換個角度來說不管是 ConcretComponent 還是 ConcreteDecorator它們都是最頂層 Component 基類用戶代碼可以把它們統一看作 Component 來處理這樣帶來的更深一層好處就是「裝飾者」對象對「被裝飾者」對象的功能職責擴展對用戶代碼來說是完全透明的因為用戶代碼引用的都是 Component所以就不會因為「被裝飾者」對象在被裝飾後引用它的用戶代碼發生錯誤實際上不會有任何影響因為裝飾前後用戶代碼引用的都是 Component 類型的對象這真是太完美了!「裝飾模式」通過繼承實現統一了「裝飾者」和「被裝飾者」的接口通過組合獲得了在運行時動態擴展「被裝飾者」對象的能力
我們再舉個生活中的例子俗話說人在衣著馬在鞍把這用「裝飾模式」的語境翻譯一下人通過漂亮的衣服裝飾後男人變帥了女人變漂亮了對應上面的類圖這裡「人」對應於 ConcreteComponent而「漂亮衣服」則對應於 ConcreteDecorator換個角度來說人和漂亮衣服組合在一起【HAS A】有了帥哥或美女但是他們還是人【IS A】還要做人該做的事情但是可能會對異性更有吸引力了(擴展功能)!
上方 Justin 前輩其文章的「煮咖啡」示例 []是引用自 OReilly 出版社的「Head First 設計模式」這本書的第三章 []該文的煮咖啡類圖中HouseBlend (家常咖啡)DarkRoast (深度烘培咖啡)Espresso (意大利特濃咖啡)Decaf (無咖啡因咖啡)這四種咖啡 (飲料)即為「被裝飾者」等同本帖上圖 中的 ConcreteComponent 類該文類圖中的 CondimentDecorator 抽象類等同本帖上圖 中最重要的 Decorator 抽象類亦即「裝飾者」的抽象定義該文類圖中的 MilkMochaSoyWhip 這四種調料 (調味品)即為具體的「裝飾者」亦即在本帖一開始提到這四種調料可彈性地 (flexible) 混合著搭配使用而且是在「執行」時期「動態」地進行擴展亦即動態地裝飾 HouseBlendDarkRoasEspressoDecaf 這四種咖啡
接下來我們用另一個簡單的例子來實現 Decorator 模式並改用 ASPNET 網頁程序來實現類圖及代碼如下方圖 所示執行結果如下圖 所示
此為一個西餐牛排館的計費程序這間牛排館有兩種主菜 牛排和豬排此為「被裝飾者」有四種副菜 面條生菜沙拉飲料 (熱飲或冷飲)甜點此為「裝飾者」客人點餐時可點一種主菜副菜可點一份可點多份也可不點 (有彈性地將多個「裝飾者」混合著搭配)每樣主菜和副菜都有各自的價格全部相加後即為一或多位客人的用餐費用而且我們希望將來這個計費程序寫好後未來即使要修改價格或添加新的主菜和副菜時都不用再修改既有的程序
圖 示例 _Steakaspxcs 的 Class Diagram
_Steakaspxcs
using System;
using blogsWizardWusample; //客戶端程序調用服務器端的組件和類
//客戶端程序
public partial class __Steak : SystemWebUIPage
{
protected void Page_Load(object sender EventArgs e)
{
//點一客豬排不需要副菜並列出它的描述與價格
Meal meal = new PorkChop();
//Meal meal = new BeefSteak();
ResponseWrite(mealDescription + = $ + st() +
);
//點一客豬排副菜只要面條並列出它的描述與價格
Meal meal = new PorkChop();
meal = new Noodle(meal); //用 Noodle 裝飾它 (運行時動態地增加職責)
ResponseWrite(mealDescription + = $ + st() +
);
//點一客豬排因為這個人食量大副菜要兩份面條一杯飲料(熱飲)一盤甜點並列出它的描述與價格
Meal meal = new PorkChop();
meal = new Noodle(meal); //用 Noodle 裝飾它
meal = new Noodle(meal); //用第二個 Noodle 裝飾它
meal = new Drink(meal true); //用 Drink 裝飾它
meal = new Dessert(meal); //用 Dessert 裝飾它
ResponseWrite(mealDescription + = $ + st() +
);
//第四個人不吃豬肉因此主菜改點一客牛排副菜要一盤沙拉一杯飲料(冷飲)並列出它的描述與價格
Meal meal = new BeefSteak();
meal = new Salad(meal); //用 Salad 裝飾它 (運行時動態地增加職責)
meal = new Drink(meal false); //用 Drink 裝飾它
ResponseWrite(mealDescription + = $ + st() +
);
}
}
//服務器端程序
namespace blogsWizardWusample
{
//裝飾者(副菜)被裝飾者(主菜)的共同基類類似前一例的 Component 抽象類
abstract class Meal
{
protected string description = 西餐;
public virtual string Description
{
get { return description; }
set { description = value; }
}
abstract public int cost(); //必須在子類實作的抽象方法除非該個子類也是抽象類
}
//主菜(被裝飾者)類似前一例的 ConcreteComponent 類
class PorkChop : Meal //主菜 豬排以後執行時才會動態被副菜裝飾
{
public PorkChop() //構造函數
{
Description = 豬排;
}
public override int cost() //具體對象的操作
{
return ; //豬排的價格現在不需要管「副菜」的價格直接返回豬排的價格即可
}
}
//主菜(被裝飾者)類似前一例的 ConcreteComponent 類
class BeefSteak : Meal //主菜 牛排以後執行時才會動態被副菜裝飾
{
public BeefSteak() //構造函數
{
Description = 牛排;
}
public override int cost() //具體對象的操作
{
return ; //牛排的價格現在不需要管「副菜」的價格直接返回牛排的價格即可
}
}
//裝飾者類似前一例的 Decorator 抽象類此為所有「副菜(裝飾者)」的父類
//功能一未來所有可能添加的副菜其共同的屬性行為都可放在這個類裡
//功能二用來「分類」哪些是主菜哪些是副菜
abstract class CondimentDecorator : Meal //副菜用來「動態」裝飾主菜和 Meal 基類亦即在運行時動態地替他們增加職責
{
//以父類聲明的字段
//實現 UML Class Diagram 的 Aggregation指向 Meal 對象的指針
protected Meal meal;
public override string Description
{
get { return mealDescription + + + description; }
set { description = value; }
}
}
//副菜(裝飾者)具體裝飾類 面條用來動態裝飾主菜 牛排或豬排
class Noodle : CondimentDecorator //類似前一例的 ConcreteDecoratorA
{
public Noodle(Meal meal) //構造函數
{
Description = 面條;
al = meal;
}
public override int cost() //具體裝飾對象 A 的操作
{
return + st();
}
}
//副菜(裝飾者)具體裝飾類 沙拉用來動態裝飾主菜 牛排或豬排
class Salad : CondimentDecorator //類似前一例的 ConcreteDecoratorB
{
public Salad(Meal meal) //構造函數
{
Description = 生菜沙拉;
al = meal;
}
public override int cost() //具體裝飾對象 B 的操作
{
return + st();
}
}
//副菜(裝飾者)具體裝飾類 飲料用來動態裝飾主菜 牛排或豬排
class Drink : CondimentDecorator //類似前一例的 ConcreteDecoratorC
{
private bool isHot;
public bool IsHot
{
get { return isHot; }
}
public Drink(Meal meal bool isHot) //構造函數
{
Description = (isHot) ? 熱飲 : 冷飲;
al = meal;
thisisHot = isHot;
}
public override int cost() //具體裝飾對象 C 的操作冷飲比熱飲多收 塊錢
{
return (isHot) ? ( + st()) : ( + st());
}
}
//副菜(裝飾者)具體裝飾類 甜點用來動態裝飾主菜 牛排或豬排
class Dessert : CondimentDecorator //類似前一例的 ConcreteDecoratorD
{
public Dessert(Meal meal) //構造函數
{
Description = 甜點;
al = meal;
}
public override int cost() //具體裝飾對象 D 的操作
{
return + st();
}
}
} // end of namespace
圖 示例 _Steakaspxcs 的執行結果擴展功能時主菜和副菜可任意混合搭配使用
原理與本帖之前提過的相同將來店裡若要推出 羊排魚排…等新的主菜 (被裝飾者) 時只要增加一個類去繼承 Meal 這個抽象的基類即可若要推出新的副菜 (裝飾者) 也一樣只要增加一個類去繼承 CondimentDecorator 抽象類即可我們透過這個重要的 CondimentDecorator 抽象類實現 UML 的 Aggregation (聚合) 觀念以「裝飾模式」取代傳統「繼承」的做法也同時降低了「主菜」「副菜」這兩種類之間的耦合度
這個 CondimentDecorator 抽象類的作用還可將未來所有種類的副菜一些「共同的」行為或職責寫在裡面而且它還可用來區分哪些類是主菜哪些類是副菜因為主菜副菜都是繼承自 Meal 這個最頂層的基類若沒有這個 CondimentDecorator 抽象類將來 Class Diagram 變得很復雜時會不易分辨某個類是主菜或副菜
此外也因為主菜 (被裝飾者)副菜 (裝飾者) 都是繼承自 Meal 這個最頂層的基類所以客戶端程序 (Page_Load) 在引用他們的對象時把 new 出來的主菜實例new 出來的副菜實例再拋入其他副菜實例的構造函數中也會相容不會發生錯誤在相互引用和「運行」時期的「動態」擴展上變得很有彈性
最後補充一點Decorator 模式中裝飾者類對象在組合時的「順序」亦很重要本帖圖 中ConcreteDecoratorAConcreteDecoratorBConcreteDecoratorC 等具體裝飾者在動態添加和組合時在某些實際應用中必須依照一定的順序若順序相反可能會導致程序執行結果不合理但本帖所提供的幾個例子中並不需要考慮到這點
Decorator Pattern 適用的情景
你擁有一個已存在的組件類卻無法繼承它 (subclassing)
能夠動態地為對象添加職責 (添加狀態和行為)
改變類中的成員和行為但不影響其他對象
希望能便於職責的撤消
不想用「繼承」來擴展行為其中一種原因是避免當一些功能要交叉搭配引用時單獨用「繼承」來設計會產生太多的子類太復雜的類圖結構 [] []另一種考量可能是因為類的定義被隱藏或類的定義不能用於生成子類
Decorator Pattern 的優點
可避免單獨使用「繼承」時在擴展時不夠彈性且可能衍生過多的子類
擴展時不需要修改既有的代碼
可在執行時期動態地添加新行為 (職責)
Decorator Pattern 的缺點
可能會在程序中出現許多的小型類亦即需要編寫很多 ConcreteDecorator 類 (具體裝飾者)
若過度使用 Decorator 模式會讓程序邏輯變得很復雜
別人較不易理解設計方式及代碼排查故障追蹤和調試也比較困難
Decorator Pattern 的其他特性
每個要裝飾的功能都放在單獨的類中
我們可以用無數個裝飾者去包裝一個組件
「裝飾者」可以在「被裝飾者」前面或後面添加自己的行為甚至將「被裝飾者」的行為整個取代掉以達到特定的目的
「被裝飾者」並不需要知道它已經被「裝飾」過了亦即 Component 類 (對象) 並不需要知道 Decorator 類 (對象) 的存在且 Decorator 也僅僅認識 Component
最後再補充在 Java I/ONET I/ONET SystemWindowsControls 命名空間中很多地方都有用到 Decorator 模式來設計 [] [] []例如我們查詢 MSDN Library會發現 NET x 版本的 Windows Forms 其 API 裡甚至還有增加一個新的 Decorator 類如下
SystemObject
SystemWindowsThreadingDispatcherObject
SystemWindowsDependencyObject
SystemWindowsMediaVisual
SystemWindowsUIElement
SystemWindowsFrameworkElement
SystemWindowsControlsDecorator
SystemWindowsControlsViewbox
SystemWindowsControlsDecorator 類
提供在單個子元素(如 Border 或 Viewbox)上或周圍應用效果的元素的基類
cn/library/ntrolsdecoratoraspx
cn/library/ntrolsdecorator(VS)aspx
以下列出 Decorator Pattern用於實際程序的四種方式 []
裝飾模式非常適合於「圖形程序」亦適合於「視頻和聲音」比如視頻流可用不同的比率進行壓縮而聲音可以輸入給同聲傳譯服務
裝飾模式如此有用以致於 NET 中已經有實際的 Decorator 類了如剛才提到的在 SystemWindowsControls 命名空間中提供了一些基類可用於給其他的圖形元素添加特殊效果如Border 類和 Viewbox 類
在如今的移動設備上Web 浏覽器及其他應用中也大量運用 Decorator Pattern比如這些設備上可以創建適合小屏幕顯示的對象跟標准桌面機上的浏覽器相比這些屏幕可能也含有滾動條但不含有標題欄 []
從更一般的層次上NET 的 I/O API 中到處都是裝飾模式 []如下方的體系
SystemObject
SystemMarshalByRefObject
SystemIOStream
SystemIOBufferedStream
SystemIOFileStream
SystemIOMemoryStream
SystemNetSocketsNetworkStream
SystemSecurityCryptographyCryptoStream
上方體系的最後五個子類有的就裝飾了 Steram 這個父類 (如BufferedStream 類)因為它們從它繼承同時又含有 Stream 類的實例而且這個實例是子類被構造時傳遞進去的 (如同本帖第二個西餐店的示例中副菜的構造函數的寫法)如下方 MSDN Library 代碼的 BufferedStream 類其構造函數的第一個參數即為其父類 SystemIOStream 的變量 stream
BufferedStream 構造函數
public BufferedStream(
Stream stream
)
我們再舉個生活中的例子俗話說人在衣著馬在鞍把這用「裝飾模式」的語境翻譯一下人通過漂亮的衣服裝飾後男人變帥了女人變漂亮了對應上面的類圖這裡「人」對應於 ConcreteComponent而「漂亮衣服」則對應於 ConcreteDecorator換個角度來說人和漂亮衣服組合在一起【HAS A】有了帥哥或美女但是他們還是人【IS A】還要做人該做的事情但是可能會對異性更有吸引力了(擴展功能)!
上方 Justin 前輩其文章的「煮咖啡」示例 []是引用自 OReilly 出版社的「Head First 設計模式」這本書的第三章 []該文的煮咖啡類圖中HouseBlend (家常咖啡)DarkRoast (深度烘培咖啡)Espresso (意大利特濃咖啡)Decaf (無咖啡因咖啡)這四種咖啡 (飲料)即為「被裝飾者」等同本帖上圖 中的 ConcreteComponent 類該文類圖中的 CondimentDecorator 抽象類等同本帖上圖 中最重要的 Decorator 抽象類亦即「裝飾者」的抽象定義該文類圖中的 MilkMochaSoyWhip 這四種調料 (調味品)即為具體的「裝飾者」亦即在本帖一開始提到這四種調料可彈性地 (flexible) 混合著搭配使用而且是在「執行」時期「動態」地進行擴展亦即動態地裝飾 HouseBlendDarkRoasEspressoDecaf 這四種咖啡
接下來我們用另一個簡單的例子來實現 Decorator 模式並改用 ASPNET 網頁程序來實現類圖及代碼如下方圖 所示執行結果如下圖 所示
此為一個西餐牛排館的計費程序這間牛排館有兩種主菜 牛排和豬排此為「被裝飾者」有四種副菜 面條生菜沙拉飲料 (熱飲或冷飲)甜點此為「裝飾者」客人點餐時可點一種主菜副菜可點一份可點多份也可不點 (有彈性地將多個「裝飾者」混合著搭配)每樣主菜和副菜都有各自的價格全部相加後即為一或多位客人的用餐費用而且我們希望將來這個計費程序寫好後未來即使要修改價格或添加新的主菜和副菜時都不用再修改既有的程序
圖 示例 _Steakaspxcs 的 Class Diagram
_Steakaspxcs
using System;
using blogsWizardWusample; //客戶端程序調用服務器端的組件和類
//客戶端程序
public partial class __Steak : SystemWebUIPage
{
protected void Page_Load(object sender EventArgs e)
{
//點一客豬排不需要副菜並列出它的描述與價格
Meal meal = new PorkChop();
//Meal meal = new BeefSteak();
ResponseWrite(mealDescription + = $ + st() +
);
//點一客豬排副菜只要面條並列出它的描述與價格
Meal meal = new PorkChop();
meal = new Noodle(meal); //用 Noodle 裝飾它 (運行時動態地增加職責)
ResponseWrite(mealDescription + = $ + st() +
);
//點一客豬排因為這個人食量大副菜要兩份面條一杯飲料(熱飲)一盤甜點並列出它的描述與價格
Meal meal = new PorkChop();
meal = new Noodle(meal); //用 Noodle 裝飾它
meal = new Noodle(meal); //用第二個 Noodle 裝飾它
meal = new Drink(meal true); //用 Drink 裝飾它
meal = new Dessert(meal); //用 Dessert 裝飾它
ResponseWrite(mealDescription + = $ + st() +
);
//第四個人不吃豬肉因此主菜改點一客牛排副菜要一盤沙拉一杯飲料(冷飲)並列出它的描述與價格
Meal meal = new BeefSteak();
meal = new Salad(meal); //用 Salad 裝飾它 (運行時動態地增加職責)
meal = new Drink(meal false); //用 Drink 裝飾它
ResponseWrite(mealDescription + = $ + st() +
);
}
}
//服務器端程序
namespace blogsWizardWusample
{
//裝飾者(副菜)被裝飾者(主菜)的共同基類類似前一例的 Component 抽象類
abstract class Meal
{
protected string description = 西餐;
public virtual string Description
{
get { return description; }
set { description = value; }
}
abstract public int cost(); //必須在子類實作的抽象方法除非該個子類也是抽象類
}
//主菜(被裝飾者)類似前一例的 ConcreteComponent 類
class PorkChop : Meal //主菜 豬排以後執行時才會動態被副菜裝飾
{
public PorkChop() //構造函數
{
Description = 豬排;
}
public override int cost() //具體對象的操作
{
return ; //豬排的價格現在不需要管「副菜」的價格直接返回豬排的價格即可
}
}
//主菜(被裝飾者)類似前一例的 ConcreteComponent 類
class BeefSteak : Meal //主菜 牛排以後執行時才會動態被副菜裝飾
{
public BeefSteak() //構造函數
{
Description = 牛排;
}
public override int cost() //具體對象的操作
{
return ; //牛排的價格現在不需要管「副菜」的價格直接返回牛排的價格即可
}
}
//裝飾者類似前一例的 Decorator 抽象類此為所有「副菜(裝飾者)」的父類
//功能一未來所有可能添加的副菜其共同的屬性行為都可放在這個類裡
//功能二用來「分類」哪些是主菜哪些是副菜
abstract class CondimentDecorator : Meal //副菜用來「動態」裝飾主菜和 Meal 基類亦即在運行時動態地替他們增加職責
{
//以父類聲明的字段
//實現 UML Class Diagram 的 Aggregation指向 Meal 對象的指針
protected Meal meal;
public override string Description
{
get { return mealDescription + + + description; }
set { description = value; }
}
}
//副菜(裝飾者)具體裝飾類 面條用來動態裝飾主菜 牛排或豬排
class Noodle : CondimentDecorator //類似前一例的 ConcreteDecoratorA
{
public Noodle(Meal meal) //構造函數
{
Description = 面條;
al = meal;
}
public override int cost() //具體裝飾對象 A 的操作
{
return + st();
}
}
//副菜(裝飾者)具體裝飾類 沙拉用來動態裝飾主菜 牛排或豬排
class Salad : CondimentDecorator //類似前一例的 ConcreteDecoratorB
{
public Salad(Meal meal) //構造函數
{
Description = 生菜沙拉;
al = meal;
}
public override int cost() //具體裝飾對象 B 的操作
{
return + st();
}
}
//副菜(裝飾者)具體裝飾類 飲料用來動態裝飾主菜 牛排或豬排
class Drink : CondimentDecorator //類似前一例的 ConcreteDecoratorC
{
private bool isHot;
public bool IsHot
{
get { return isHot; }
}
public Drink(Meal meal bool isHot) //構造函數
{
Description = (isHot) ? 熱飲 : 冷飲;
al = meal;
thisisHot = isHot;
}
public override int cost() //具體裝飾對象 C 的操作冷飲比熱飲多收 塊錢
{
return (isHot) ? ( + st()) : ( + st());
}
}
//副菜(裝飾者)具體裝飾類 甜點用來動態裝飾主菜 牛排或豬排
class Dessert : CondimentDecorator //類似前一例的 ConcreteDecoratorD
{
public Dessert(Meal meal) //構造函數
{
Description = 甜點;
al = meal;
}
public override int cost() //具體裝飾對象 D 的操作
{
return + st();
}
}
} // end of namespace
圖 示例 _Steakaspxcs 的執行結果擴展功能時主菜和副菜可任意混合搭配使用
原理與本帖之前提過的相同將來店裡若要推出 羊排魚排…等新的主菜 (被裝飾者) 時只要增加一個類去繼承 Meal 這個抽象的基類即可若要推出新的副菜 (裝飾者) 也一樣只要增加一個類去繼承 CondimentDecorator 抽象類即可我們透過這個重要的 CondimentDecorator 抽象類實現 UML 的 Aggregation (聚合) 觀念以「裝飾模式」取代傳統「繼承」的做法也同時降低了「主菜」「副菜」這兩種類之間的耦合度
這個 CondimentDecorator 抽象類的作用還可將未來所有種類的副菜一些「共同的」行為或職責寫在裡面而且它還可用來區分哪些類是主菜哪些類是副菜因為主菜副菜都是繼承自 Meal 這個最頂層的基類若沒有這個 CondimentDecorator 抽象類將來 Class Diagram 變得很復雜時會不易分辨某個類是主菜或副菜
此外也因為主菜 (被裝飾者)副菜 (裝飾者) 都是繼承自 Meal 這個最頂層的基類所以客戶端程序 (Page_Load) 在引用他們的對象時把 new 出來的主菜實例new 出來的副菜實例再拋入其他副菜實例的構造函數中也會相容不會發生錯誤在相互引用和「運行」時期的「動態」擴展上變得很有彈性
最後補充一點Decorator 模式中裝飾者類對象在組合時的「順序」亦很重要本帖圖 中ConcreteDecoratorAConcreteDecoratorBConcreteDecoratorC 等具體裝飾者在動態添加和組合時在某些實際應用中必須依照一定的順序若順序相反可能會導致程序執行結果不合理但本帖所提供的幾個例子中並不需要考慮到這點
Decorator Pattern 適用的情景
你擁有一個已存在的組件類卻無法繼承它 (subclassing)
能夠動態地為對象添加職責 (添加狀態和行為)
改變類中的成員和行為但不影響其他對象
希望能便於職責的撤消
不想用「繼承」來擴展行為其中一種原因是避免當一些功能要交叉搭配引用時單獨用「繼承」來設計會產生太多的子類太復雜的類圖結構 [] []另一種考量可能是因為類的定義被隱藏或類的定義不能用於生成子類
Decorator Pattern 的優點
可避免單獨使用「繼承」時在擴展時不夠彈性且可能衍生過多的子類
擴展時不需要修改既有的代碼
可在執行時期動態地添加新行為 (職責)
Decorator Pattern 的缺點
可能會在程序中出現許多的小型類亦即需要編寫很多 ConcreteDecorator 類 (具體裝飾者)
若過度使用 Decorator 模式會讓程序邏輯變得很復雜
別人較不易理解設計方式及代碼排查故障追蹤和調試也比較困難
Decorator Pattern 的其他特性
每個要裝飾的功能都放在單獨的類中
我們可以用無數個裝飾者去包裝一個組件
「裝飾者」可以在「被裝飾者」前面或後面添加自己的行為甚至將「被裝飾者」的行為整個取代掉以達到特定的目的
「被裝飾者」並不需要知道它已經被「裝飾」過了亦即 Component 類 (對象) 並不需要知道 Decorator 類 (對象) 的存在且 Decorator 也僅僅認識 Component
最後再補充在 Java I/ONET I/ONET SystemWindowsControls 命名空間中很多地方都有用到 Decorator 模式來設計 [] [] []例如我們查詢 MSDN Library會發現 NET x 版本的 Windows Forms 其 API 裡甚至還有增加一個新的 Decorator 類如下
SystemObject
SystemWindowsThreadingDispatcherObject
SystemWindowsDependencyObject
SystemWindowsMediaVisual
SystemWindowsUIElement
SystemWindowsFrameworkElement
SystemWindowsControlsDecorator
SystemWindowsControlsViewbox
SystemWindowsControlsDecorator 類
提供在單個子元素(如 Border 或 Viewbox)上或周圍應用效果的元素的基類
cn/library/ntrolsdecoratoraspx
cn/library/ntrolsdecorator(VS)aspx
以下列出 Decorator Pattern用於實際程序的四種方式 []
裝飾模式非常適合於「圖形程序」亦適合於「視頻和聲音」比如視頻流可用不同的比率進行壓縮而聲音可以輸入給同聲傳譯服務
裝飾模式如此有用以致於 NET 中已經有實際的 Decorator 類了如剛才提到的在 SystemWindowsControls 命名空間中提供了一些基類可用於給其他的圖形元素添加特殊效果如Border 類和 Viewbox 類
在如今的移動設備上Web 浏覽器及其他應用中也大量運用 Decorator Pattern比如這些設備上可以創建適合小屏幕顯示的對象跟標准桌面機上的浏覽器相比這些屏幕可能也含有滾動條但不含有標題欄 []
從更一般的層次上NET 的 I/O API 中到處都是裝飾模式 []如下方的體系
SystemObject
SystemMarshalByRefObject
SystemIOStream
SystemIOBufferedStream
SystemIOFileStream
SystemIOMemoryStream
SystemNetSocketsNetworkStream
SystemSecurityCryptographyCryptoStream
上方體系的最後五個子類有的就裝飾了 Steram 這個父類 (如BufferedStream 類)因為它們從它繼承同時又含有 Stream 類的實例而且這個實例是子類被構造時傳遞進去的 (如同本帖第二個西餐店的示例中副菜的構造函數的寫法)如下方 MSDN Library 代碼的 BufferedStream 類其構造函數的第一個參數即為其父類 SystemIOStream 的變量 stream
BufferedStream 構造函數
public BufferedStream(
Stream stream
From:http://tw.wingwit.com/Article/program/net/201311/12524.html