討論區快速選單
知識庫快速選單
網路投保旅行平安險 網路投保旅行平安險
[ 回上頁 ] [ 討論區發言規則 ]
介面繼承 與 組合代替繼承
更改我的閱讀文章字型大小
作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/3 下午 12:32:17
此文延伸自「強制實作(http://www.programmer-club.com.tw/ShowSameTitleN/c/46861.html)」一文,若解說上有不明究理的地方可以先回頭看看。

在「強制實作」提到用 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() 不寫,編譯上也不會有問題,但那是錯誤的,執行時會當掉。
作者 : cxxlman(CxxlMan) C++優秀好手貼文超過1000則
[ 貼文 1003 | 人氣 3227 | 評價 1260 | 評價/貼文 1.26 | 送出評價 27 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/10/3 下午 12:32:50
接下來是 介面繼承 加上 組合代替繼承 的改裝版本

#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++ 的規格之下「被迫」的選擇,若這是純虛擬函數只能實作一次的目的,也太有心機了吧,可以導正回來消滅類別繼承,只是麻煩一些就是了。

 板主 : 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.125