2017年10月12日 星期四

用 C++11 完美詮釋 Decorator pattern

Decorator UML class diagram.svg

By Trashtoy - My own work, written with text editor., Public Domain, Link

Decorator pattern 的類圖表明了所有處理、使用的對象都是 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)

#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 關鍵字的苦腦。
很幸運地,以上三點可以用 介面繼承+組合代替繼承 加以解決,以下重新改寫的範例:

#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;
}


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







沒有留言:

張貼留言