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