2017年10月3日 星期二

介面繼承 與 組合代替繼承

此文延伸自「虛擬函數多層強制實作」一文,若解說上有不明究理的地方可以先回頭看看。

在「虛擬函數多層強制實作」提到用 PROTOTYPE_BASE_CRTP 這個巨集來取代直接繼承的做法,可強制子孫類別都得強制實作某些虛擬函數,可是現在問題是如何強制必須使用這個巨集,所以只是把一個難題換成另一個難題而已。

所以還是得回歸 C++ 本身,C++ 只提供一層的強制實作,這應該有個光明正大的理由,文章最後有個看法也許可以成立。

PROTOTYPE_BASE_CRTP 巨集只是利用 C++ 的多重繼承在每個子孫類別再繼承一次純虛擬函數,以達到強制實作的目的,若這方式不可行,還有其它的方法嗎?

C++ 的純虛擬函數一次性實作,只要不實作就可以不斷的延續到子孫類別,所以只要採用「介面繼承」的方式就可以達到強制實作的目的。但類別資料繼承的部份要怎麼解決,不是程式設計界有句名言「組合代替繼承」,就用這兩招來試試。


先設計要被改裝的程式樣本

#include <memory>
#include <iostream>

using namespace std;

class Prototype
{
public:
  Prototype() {}
  ~Prototype() {}
  // 要求子孫類別都須實作的函數
  virtual Prototype *Clone() = 0;
};

class CA :virtual public Prototype
{
  int m_V;
public:
  CA(int a_V) 
    :m_V(a_V)
  {}
  ~CA() {}

  void Set_a_V(int v)
  {
    m_V = v;
  }
  int Get_a_V()
  {
    return m_V;
  }

  virtual Prototype *Clone() override
  {
    return new CA(m_V);
  }
};

class CB :public CA
{
  int m_V;
public:
  CB(int a_V,int b_V) 
    :CA(a_V),  m_V(b_V)
  {}
  ~CB() {}

  void Set_b_V(int v)
  {
    m_V = v;
  }
  int Get_b_V()
  {
    return m_V;
  }

  virtual Prototype *Clone() override
  {
    return new CB(Get_a_V(),m_V);
  }

};

int main()
{
  shared_ptr<Prototype> Prototype_Ptr(new CB(1,2));

  shared_ptr<CB> CB_Ptr ( dynamic_cast<CB*>(Prototype_Ptr->Clone()) );

  cout << "a_V: " << CB_Ptr->Get_a_V() << endl
       << "b_V: " << CB_Ptr->Get_b_V() << endl;


  return 0;
}


以上是三層類別繼承簡單架構,即使 CB::Clone() 不寫,編譯上也不會有問題,但那是錯誤的,執行時會當掉。


接下來是 介面繼承 加上 組合代替繼承 的改裝版本

#include <memory>
#include <iostream>

using namespace std;

// 介面
class Prototype
{
public:
  Prototype() {}
  ~Prototype(){}
  virtual Prototype *Clone() = 0;
};

// 介面繼承
class IA:virtual public Prototype
{
public:
  IA() {}
  ~IA() {}

  virtual void Set_a_V(int v) = 0;
  virtual int Get_a_V() = 0;

  static IA *Create(int a_V);
};

// 實作
class CA :public IA
{
  int m_V;

  virtual void Set_a_V(int v) override
  {
    m_V = v;
  }
  virtual int Get_a_V() override
  {
    return m_V;
  }

  virtual Prototype *Clone() override
  {
    IA *pIA = IA::Create(m_V);
    return pIA;
  }
public:
  CA() {}
  ~CA() {}
};

IA *IA::Create(int a_V)
{
  // 照理不應該知道實作類別,這裡是便宜行事
  // 正常情況應使用工廠模式來取得物件

  IA *pIA = new CA; 
  pIA->Set_a_V(a_V);
  return pIA;
}

// 介面繼承
class IB :public IA
{
public:
  IB() {}
  ~IB() {}

  virtual void Set_b_V(int v) = 0;
  virtual int Get_b_V() = 0;
  static IB *Create(int a_V, int b_V);
};

// 實作
class CB :public IB
{
  int m_V;
  // 代替繼承,且是包裹父類別的介面
  // 若要包裹實作類別,除了有可能無法取得
  // 外,少了額外替換的機會
  shared_ptr<IA> IA_Ptr; 

  virtual void Set_a_V(int v) override
  {
    IA_Ptr->Set_a_V(v);
  }
  virtual int Get_a_V() override
  {
    return IA_Ptr->Get_a_V();
  }

  virtual void Set_b_V(int v) override
  {
    m_V = v;
  }
  virtual int Get_b_V() override
  {
    return m_V;
  }

  virtual Prototype *Clone() override
  {
    IA *pIB = IB::Create(m_V, IA_Ptr->Get_a_V());
    return pIB;
  }

public:
  CB() 
    :IA_Ptr(IA::Create(0))
  {}
  ~CB() {}
};

IB *IB::Create(int a_V, int b_V)
{
  IB *pIB = new CB;
  pIB->Set_a_V(a_V);
  pIB->Set_b_V(b_V);
  return pIB;
}

int main()
{
  shared_ptr<Prototype> Prototype_Ptr(IB::Create(1, 2));

  shared_ptr<IB> IB_Ptr ( dynamic_cast<IB*>(Prototype_Ptr->Clone()) );

  cout << "a_V: " << IB_Ptr->Get_a_V() << endl
       << "b_V: " << IB_Ptr->Get_b_V() << endl;

  return 0;
}


以上就字面上要比樣本來得複雜一些,但搞清楚來龍去脈應該也還能掌握。

使用 介面繼承 和 組合代替繼承 並不是看在這樣會來得優秀,而是在 C++ 的規格之下「被迫」的選擇,若這是純虛擬函數只能實作一次的目的,也太有心機了吧,可以導正回來消滅類別繼承,只是麻煩一些就是了。










沒有留言:

張貼留言