討論區快速選單
知識庫快速選單
程式設計俱樂部Facebook粉絲團 網路投保旅行平安險
[ 回上頁 ] [ 討論區發言規則 ]
用 C++11 完美詮釋 Decorator pattern
更改我的閱讀文章字型大小
作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/12 下午 04:58:08
Decorator pattern 的類圖(https://commons.wikimedia.org/wiki/File:Decorator_UML_class_diagram.svg#/media/File:Decorator_UML_class_diagram.svg)表明了所有處理、使用的對象都是 Component 介面,能夠操作的只有 Component 開放的功能,也就是 operation(),任何 Component 的實作類別都必須依自己的情況實作 operation()。其中有一個 Component 的延伸介面 Decorator,對 operation() 有一個明確的定義--除了做自己的事之外,還要呼叫的包裹對象的 operation()。

為什麼叫 Decorator pattern,因所設定的目標是在執行時期為所處理的物件附加行為,且不會影所處理物件,有人拿來當作是類別繼承的替代模式,但兩者之間還是有根本性的不同,類別繼承可以增加功能(增加成員函數),但 Decorator pattern 只能增加行為,也就是為 Component::operation() 增加操作的內容。
 
在 C++11 以前(也就是 C++98)做起來都不夠完美,因對虛擬函數少了 final 關鍵字的支援,先看看以下的範例(範例取材自 Head First Design Patterns In C Sharp)

作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/12 下午 04:58:55
#include <memory>
#include <iostream>

using namespace std;

// Component (interface)
// 飲料介面
class IBeverage
{
public:
 IBeverage(){}
 virtual ~IBeverage(){}

 // 回報費用
 virtual float Cost() const = 0;
};

// ConcreteComponent
// 飲料的實作-咖啡
class CCoffee:public IBeverage
{
 // 回報費用
 virtual float Cost() const override
 {
  return 10;
 }
public:
 CCoffee(){}
 ~CCoffee(){}
};

// Decorator (interface)
// 裝飾者介面
class IDecorator :public IBeverage
{
 // 包裹被裝飾的物件
 shared_ptr<IBeverage> IBeverage_Ptr;

 // 子類別必須提供增加的費用
 virtual float AddCost() const = 0;

 // 回報費用
 // 重新定義行為且不希望被修改
 virtual float Cost() const override final
 {
  return IBeverage_Ptr->Cost() + AddCost();
 }

public:
 IDecorator(const shared_ptr<IBeverage> &IBeverage_Arg)
  :IBeverage_Ptr(IBeverage_Arg)
 {}
 ~IDecorator(){}
};

class Mocha :public IDecorator
{
 // 子類別必須提供增加的費用
 virtual float AddCost() const override
 {
  return 1.3;
 }
public:
 Mocha(const shared_ptr<IBeverage> &IBeverage_Arg)
  :IDecorator(IBeverage_Arg)
 {}
 ~Mocha() {}
};

int main()
{
 // 產生咖啡物件
 shared_ptr<IBeverage> Coffee_Ptr(new CCoffee);

 // 為咖啡附加 Mocha
 shared_ptr<IBeverage> Mocha1_Ptr(new Mocha(Coffee_Ptr));
 // 雙倍 Mocha 咖啡
 shared_ptr<IBeverage> Mocha2_Ptr(new Mocha(Mocha1_Ptr));

 cout << "雙倍 Mocha 咖啡的費用是:" << Mocha2_Ptr->Cost() << endl;

 return 0;
}

看看 IDecorator::Cost() 的定義

 // 回報費用
 // 重新定義行為且不希望被修改
 virtual float Cost() const override final
 {
  return IBeverage_Ptr->Cost() + AddCost();
 }

若沒有 final 這個關鍵字,就得用以下兩個方法:

1. 道德勸說 IDecorator 的實作類別不要改寫
2. IDecorator 不要實作,交給 IDecorator 的實作類別去寫,但這樣 IDecorator 要做什麼

所以少了 final 就是不完美。

但這樣就完美了嗎,看看 main() 出現 new CCoffee,而 CCoffee 就直接由 IBeverage 延伸而來,若有人想擴展 CCoffee 勢必要用類別繼承,類別繼承至少有以下三點問題:

1. 父子類別處於緊張狀態,父類別要修改時會顧忌會不會影響子類別
2. 依 C++ 的標準,父類別若實作了某個要強制實作的純虛擬函數,子類別就不能再被要求要強制實怍。
3. 子類別有改寫虛擬函數的權力,父類別要面臨要不要使用 final 關鍵字的苦腦。

很幸運地,以上三點可以用 介面繼承+組合代替繼承 加以解決,以下重新改寫的範例:
作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/12 下午 05:00:13
#include <memory>
#include <iostream>

using namespace std;

// Component (interface)
// 飲料介面
class IBeverage
{
public:
 IBeverage() {}
 virtual ~IBeverage() {}

 // 回報費用
 virtual float Cost() const = 0;
};

// 介面繼承
// 咖啡介面
class ICoffee :public IBeverage
{
public:
 // 喝咖啡的動作
 virtual void Drink() const = 0;

 inline static ICoffee *Create();
};

// ConcreteComponent
// 咖啡的實作
class CCoffee :public ICoffee
{
 // 回報費用
 virtual float Cost() const override
 {
  return 10;
 }

 // 喝咖啡的動作
 virtual void Drink() const override
 {
  cout << "咖啡好好喝" << endl;
 }
public:
 CCoffee() {}
 ~CCoffee() {}
};

ICoffee *ICoffee::Create()
{
 // 為了簡化處理直接產生 CCoffee 實
 // 例,實際上應該用工廠模式之類的方法產生
 return new CCoffee;
}


// 介面繼承
class ITaiwanCoffee :public ICoffee
{
public:
 // 擴充的功能
 virtual void Go() const = 0;

 inline static ITaiwanCoffee *Create();
};

// 實作繼承
class CTaiwanCoffee :public ITaiwanCoffee
{
 // 用組合替代類別繼承
 shared_ptr<ICoffee> ICoffee_Ptr;

 // 回報費用
 virtual float Cost() const override
 {
  return 13;
 }

 // 喝咖啡的動作
 virtual void Drink() const override
 {
  ICoffee_Ptr->Drink();
 }

 virtual void Go() const override
 {
  cout << "Taiwan Go Go Go!!" << endl;
 }
public:
 CTaiwanCoffee()
  :ICoffee_Ptr(ICoffee::Create())
 {}
 ~CTaiwanCoffee(){}
};

ITaiwanCoffee *ITaiwanCoffee::Create()
{
 return new CTaiwanCoffee;
}


作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/12 下午 05:00:53
// Decorator (interface)
// 裝飾者介面
class IDecorator :public IBeverage
{
 // 包裹被裝飾的物件
 shared_ptr<IBeverage> IBeverage_Ptr;

 // 子類別必須提供增加的費用
 virtual float AddCost() const = 0;

 // 回報費用
 // 重新定義行為且不希望被修改
 virtual float Cost() const override final
 {
  return IBeverage_Ptr->Cost() + AddCost();
 }

public:
 IDecorator(const shared_ptr<IBeverage> &IBeverage_Arg)
  :IBeverage_Ptr(IBeverage_Arg)
 {}
 ~IDecorator() {}
};

// 介面繼承
class IMocha :public IDecorator
{
protected:
 IMocha(const shared_ptr<IBeverage> &IBeverage_Arg)
  :IDecorator(IBeverage_Arg)
 {}
public:
 inline static IMocha *Create(const shared_ptr<IBeverage> &IBeverage_Arg);
};

// 實作繼承
class CMocha :public IMocha
{
 // 子類別必須提供增加的費用
 virtual float AddCost() const override
 {
  return 1.3f;
 }
public:
 CMocha(const shared_ptr<IBeverage> &IBeverage_Arg)
  :IMocha(IBeverage_Arg)
 {}
 ~CMocha() {}
};

IMocha *IMocha::Create(const shared_ptr<IBeverage> &IBeverage_Arg)
{
 return new CMocha(IBeverage_Arg);
}

int main()
{
 // 產生咖啡物件
 // 並轉換使用場合所須要適當的角色介面
 shared_ptr<ITaiwanCoffee> ITaiwanCoffee_Ptr(ITaiwanCoffee::Create());
 shared_ptr<ICoffee> ICoffee_Ptr(ITaiwanCoffee_Ptr);
 shared_ptr<IBeverage> IBeverage_Ptr(ICoffee_Ptr);

 // 為咖啡附加 Mocha
 shared_ptr<IBeverage> Mocha1_Ptr(IMocha::Create(IBeverage_Ptr));
 // 雙倍 Mocha 咖啡
 shared_ptr<IBeverage> Mocha2_Ptr(IMocha::Create(Mocha1_Ptr));

 cout << "雙倍 Mocha 咖啡的費用是:" << Mocha2_Ptr->Cost() << endl;

 // 執行擴增的個別功能
 ICoffee_Ptr->Drink();
 ITaiwanCoffee_Ptr->Go();

 return 0;
}

介面繼承+組合代替繼承 真的能完全取代類別繼承嗎?也就是說類別繼承能做到的 介面繼承+組合代替繼承 也能做到嗎?就目前來看是的,只是寫法要麻煩許多,範例中還未加上工廠模式,否則會更複雜。但卻能讓父子類別從強力耦合關係中解放出來,大大強化了彈性和日後的維謢

 板主 : simula
 > C++ - 討論區
 - 最近熱門問答精華集
 - 全部歷史問答精華集
 - C++ - 知識庫
  ■ 全站最新Post列表
  ■ 我的文章收藏
  ■ 我最愛的作者
  ■ 全站文章收藏排行榜
  ■ 全站最愛作者排行榜
  ■  月熱門主題
  ■  季熱門主題
  ■  熱門主題Top 20
  ■  本區Post排行榜
  ■  本區評價排行榜
  ■  全站專家名人榜
  ■  全站Post排行榜
  ■  全站評價排行榜
  ■  全站人氣排行榜
 請輸入關鍵字 
  開始搜尋
 
Top 10
評價排行
C++
1 Raymond 13050 
2 青衫 4760 
3 simula 4690 
4 coco 4030 
5 白老鼠(Gary) 3670 
6 ozzy 2540 
7 Ben 2250 
8 Anderson 1960 
9 windblown 1650 
10 Kenny 1560 
C++
  專家等級 評價  
  一代宗師 10000  
  曠世奇才 5000  
  頂尖高手 3000  
  卓越專家 1500  
  優秀好手 750  
Microsoft Internet Explorer 6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2018 程式設計俱樂部 http://www.programmer-club.com.tw/
0.15625