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