設計模式(四)裝飾模式
老樣子,先來看一個需求:
現在要求寫一個可以給人搭配不同的服飾的系統,比如類似QQ、網路遊戲或論壇都有的Avatar系統。怎麼開發?
第一個版本可能是這樣的——
第一版
結構圖:
Person類:
class Person{ private string name; public Person(string name){ this.name=name; } public void WearTshirts(){ Console.Write("大T恤"); } public void WearBigTrouser(){ Console.Write("垮褲"); } public void WearSuit(){ Console.Write("西裝"); } public void WearSneakers(){ Console.Write("破球鞋"); } public void WearTie(){ Console.Write("領帶"); } public void WearLeatherShoes(){ Console.Write("皮鞋"); } public void WearShow(){ Console.Write("裝扮的{0}",name); } }
客戶端程式碼:
static void Main(string[] args){ Person xc = new Person("小菜"); Console.WriteLine("\n第一種裝扮:"); xc.WearTShirts(); xc.WearBigTrouser(); xc.WearSneakers(); xc.Show(); Console.WriteLine("\n第二種裝扮:"); xc.WearSuit(); xc.WearTie(); xc.WearLeatherShoes(); xc.Show(); Console.Read(); }
很簡單,功能是實現了,但是考慮——
現在如果需要增加“超人”的裝扮,如何做?
你可能會說,改一下“Person”類就行了。
誒?不對吧,想想上一章我們講的幾大原則,這樣做是不是違反了開放-封閉原則?
嗯,那把這些服飾都寫成子類就好了——
第二版
Person類:
class Person{ private string name; public Person (string name){ this.name=name; } public void Show(){ Console.WriteLine("裝扮的{0}",name); } }
服飾抽象類:
// 服飾
abstract class Finery{
public abstract void Show();
}
各種服飾子類:
// 大T恤
class TShirts : Finery{
public override void Show(){
Console.Write("大T恤");
}
}
// 垮褲
class BigTrouser : Finery{
public override void Show(){
Console.Write("大T恤");
}
}
// 其餘類類似,省略
客戶端程式碼:
static void Main(string[] args){
Person xc=new Person("小菜");
Console.WriteLine("\n第一種裝扮:");
Finery dtx=new Tshirts();
Finery kk =new BigTrouser();
Finery pqx = new Sneakers();
dtx.Show();
kk.Show();
pqx.Show();
xc.Show();
Console.WriteLine("\n第二種裝扮:");
Finery xz=new Suit();
Finery ld =new Tie();
Finery px = new LeatherShoes();
xz.Show();
ld.Show();
px.Show();
xc.Show();
Console.Read();
}
這樣來,如果要加超人裝扮,只需要增加子類就可以了。
僅僅是這樣就可以了嗎?不,還不夠,僅僅用了繼承和抽象類不能算用好了面向物件。
通過服飾組合出一個有個性的人完全可以有無數種方案,並非是固定的。 我們需要把所需的功能按正確的順序串聯起來進行控制。
這就是我們接下來要講的——
裝飾模式
裝飾模式(Decorator),動態地給一個物件新增一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活。
來看看它的結構:
Component是定義一個物件介面,可以給這些物件動態地新增職責。ConcreteComponent是定義了一個具體的物件,也可以給這個物件新增一些職責。Decorator,裝飾抽象類,繼承了Component,從外類來擴充套件Component類的功能,但對於Component來說,是無需知道Decorator的存在的,至於ConcreteDecorator就是具體的裝飾物件,起到給Component新增職責的功能。
來看看程式碼的實現。
Component類:
abstract class Component{
public abstract void Operation();
}
ConcreteComponent類:
class ConcreteComponent:Component{
public override void Operation(){
Console.WriteLine("具體物件的操作");
}
}
Decorator類:
abstract class Decorator:Component{
protected Component component;
// 設定Component
public void SetComponent(Component component){
this.component=component;
}
// 重寫Operation(),實際執行的是Component的Operation()
public override void Operation(){
if(component!=null){
component.Operation();
}
}
}
ConcreteDecoratorA/B類:
class ConcreteDetectorA:Decorator{
// 本類的獨有功能,以區別於ConcreteDecoratorB
private string addedState;
public override void Operation(){
// 首先執行原Component的Operation(),再執行本類的功能,比如addedState,
// 相當於對原Component進行了修飾
base.Operation();
addedState = "New State";
Console.WriteLine("具體裝飾物件A的操作");
}
}
class ConcreteDetectorB:Decorator{
public override void Operation(){
// 首先執行原Component的Operation(),再執行本類的功能,如AddedBehavior,
// 相當於對原Component進行了修飾
base.Operation();
AddedBehavior();
Console.WriteLine("具體裝飾物件B的操作");
}
// 本類的獨有方法,以區別於ConcreteDecoratorA
private void AddedBehavior(){}
}
客戶端程式碼:
static void Main(string[] args){
ConcreteComponent c= new ConcreteComponent();
ConcreteDecoratorA d1= new ConcreteDecoratorA();
ConcreteDecoratorB d2= new ConcreteDecoratorB();
// 裝飾的方法是:首先用ConcreteComponent例項化物件c
// 然後用ConcreteDecoratorA的例項化物件d1來包裝c
// 再用ConcreteDecoratorB的物件d2包裝d1
// 最終執行d2的Operation()
d1.SetComponent(c);
d2.SetComponent(d1);
d2.Operation();
Console.Read();
}
這裡可能不好理解,解釋一下,d2.Operation()執行時,進入:
public override void Operation(){
// 首先執行原Component的Operation(),再執行本類的功能,如AddedBehavior,
// 相當於對原Component進行了修飾
base.Operation();
AddedBehavior();
Console.WriteLine("具體裝飾物件B的操作");
}
先執行base.Operation(),base是哪個呢?看d2.Operation()的前面兩行, d1.SetComponent(c);d2.SetComponent(d1);
看一下SetComponent()函式:(繼承自Decorator)
public void SetComponent(Component component){
this.component=component;
}
也就是說,d2的base的component是d1,d1的base的component是c,那會先執行c的操作,然後a,然後b。
裝飾模式是利用SetComponent來對物件進行包裝的。這樣每個裝飾物件的實現就和如何使用這個物件分離開了,每個裝飾物件只關心自己的功能,不需要關心如何被新增到物件鏈當中。
如果只有一個ConcreteComponent類而沒有抽象的Component類,那麼Decorator類可以是ConcreteComponent的一個子類。同樣道理,如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合併成一個類。
第三版
Person類(ConcreteComponent):
class Person{
public Person() {}
private string name;
public Person(string name){
this.name=name;
}
public virtual void Show(){
Console.WriteLine("裝扮的{0}",name);
}
}
服飾類(Decorator):
class Finery:Person{
protected Person component;
// 打扮
public void Decorate(Person component){
this.component=component;
}
public override void Show(){
if(component != null)
component.Show();
}
}
具體服飾類(ComponentDecorator):
class Tshirts:Finery{
public override void Show(){
// 注意這裡順序和示例不一樣
Console.Write("大T恤");
base.Show();
}
}
class BigTrouser:Finery{
public override void Show(){
Console.Write("垮褲");
base.Show();
}
}
// 其餘類類似,省略
......
客戶端程式碼:
static void Main(string[] args){
Person xc = new Person("小菜");
Console.WriteLine("\n小菜的裝扮:");
Sneakers pqx = new Sneakers();
BigTrouser kk = new BigTrouser();
TShirts dtx = new Tshirts();
pqx.Decorate(xc);
kk.Decorate(pqx);
dtx.Decorate(kk);
dtx.Show();
Console.Read();
}
結果顯示:
小菜的裝扮:
大T恤 垮褲 破球鞋 裝扮的小菜 (注意前面程式碼裡的註釋,順序有變化!)
總結
裝飾模式是為已有功能動態地新增更多功能的一種方式。當系統需要新功能時,是向舊的類中新增新的程式碼,這些新加的程式碼通常裝飾了原有類的核心職責或主要行為。它們在主類中加入了新的欄位,新的方法和新的邏輯,從而增加了主類的複雜度。而這些新加入的東西僅僅是為了滿足一些只在某種特定情況下才會執行的特殊行為的需要。而裝飾模式卻提供了一個非常好的解決方案,它把每個要裝飾的功能放在單獨的類中,並讓這個類包裝它所要裝飾的物件。因此,當需要執行特殊行為時,客戶程式碼就可以在執行時根據需要有選擇地、按順序地使用裝飾功能包裝物件了。
裝飾模式的優點就是,把類中的裝飾功能從類中搬移去除,這樣可以簡化原有的類。它有效地把類的核心職責和裝飾功能區分開了,而且可以去除相關類中重複的裝飾邏輯。
最理想的情況,是保證裝飾類之間彼此獨立,這樣它們就可以以任意的順序進行組合了。
本章完。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
本文是連載文章,此為第四章,學習封裝變化點的策略模式。
下一章:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------