在OO中Base Class最多的應用有2種方式,第一種是interface,也就是基類是個abstract class, 最典型的例子是Factory Design Pattern. 第二種方式是Base Class 提供一種機制,然後提供幾個Virtual Function,允許使用者對這個類進行重載,從而達到“配置” (customization)的目的。 其典型的代表是 MFC,通過繼承提供訊息傳遞機制的CWin類的OnPaint等函數,實現更多更強大的功能。
這次就主要講講這兩種類的設計。
============== Abstract Interface ==============
對於Interface Class來說,基類一定是個Abstract Class. 其含義是一類物體的抽象概念。比如下面一個例子:
class Gun {
public:
void Shoot (void) = 0;
...
};
Gun是所有人的抽象概念,具體的實現起來可以為Eagle, AK47, MD5 等等。所有的槍都有射擊的功能,但是他們具體射擊的方式不同,因此只要提供一個Shoot的Interface,內容由衍生類別來實現。
在具體設計 Interface Class的時候,有幾個需要注意的內容:
1. 最好只提供 pure virtual function。 這樣做能夠使Interface Class更加單一,更加純淨,更加漂亮,更容易維護。Interface就只能是Interface,不應該提供其他任何東西。因為Interface僅僅是一個描述。
2. 最好沒有data member。Data Member往往會限制 Interface Class的靈活性。而且有Data Member,一般就要有Accessor Function,使得Interface Class不再純潔。。。。。。
3. 如果不得不用Data Member,那麼一定要提供帶參數的Constructor,並且把確省的Constructor設成private.例如:
class InterfaceClass {
private:
UINT32 _dataMember;
public:
Base() {};
...// other interface
};
class Derived : public InterfaceClass {
public:
Derived() {};
};
上面代碼中的Derived Constructor是合法的,但是由於寫代碼的人粗心大意,忘記給_dataMember附初值,很容易造成程式的崩潰。所以,對於有data member的Base Class 正確的寫法是:
class InterfaceClass {
private:
UINT32 _dataMember;
public:
Base( UINT32 data ) : _dataMember(data) {};
...//other interface
};
4. 在大部分情況下,Base Class的Destructor 一般要放到public 裡邊,並且要有實現,也就是說至少是空的實現,"{ }",即使是pure virtual destructor,這個 {}也是不能少的。因為還很多情況下,都需要通過Interface 調用Destructor來釋放object. 如:
class InterfaceClass {
public:
~InterfaceClass() {};
};
======= Derived Class的管理 =======
對於Interface Class來說,如果Derived Class太多,是很難管理的,不過我們可以通過統一的ClassManager來實現。下面是個小例子:
// Interface
class InterfaceClass {
public:
virtual void SomeInterface( void ) = 0;
private:
friend class ClassManager;
~InterfaceClass() = 0 {};
};
// First Concrete Derived Class
class Derived1 : public InterfaceClass {
public:
virtual void SomeInterface( void ) { ... };
private:
friend class ClassManager;
Derived1() {...};
~Derived1() {...};
};
// Second Concrete Derived Class
class Derived2 : public InterfaceClass {
public:
virtual void SomeInterface( void ) { ... };
private:
friend class ClassManager;
Derived2() {...};
~Derived2() {...};
};
// Class Manager
// Singleton
class ClassManager
{
public:
static ClassManager* GetInstance( void )
{
static ClassManager instance;
return &instance;
}
InterfaceClass * GetDerived1(void) { return new Derived1() };
InterfaceClass * GetDerived2(void) { return new Derived2() };
private:
ClassManager();
~ClassManager();
};
============== Concrete Base Class ==============
Concrete Base Class是使用的比較多的。遊戲中經常有的object之間,差別非常的小,這些都可以通過Concrete Base Class來抽象,提高代碼重用的效率。下面是個小例子:
class StringArray {
public:
void AddString() {...};
void SearchString() {....};
void SortString( void ) { StrategySortString(); };
...
private:
virtual StrategySortString ( void )
{ ... }; // bubble sort
};
class StringArray2 : public StringArray {
...
private:
virtual StrategySortString ( void )
{ ... }; // quick sort
};
假設我們有StringArray, 已經提供了所有基本的String Array操作,不過對於不同的Array輸入方式,刪除方式等,對應的排序方式的效率也不大相同。因此我們可以把sort設定成為 virtual,然後對於不同的StringArray的使用方式,再相對選擇正確的Derived Class就可以了。
對於Concrete Base Class 有幾點需要注意的:
1. Destructor。 相信這個不用多說了,一定要 virtual, 而且要保證資源釋放的乾淨。
2. 使用non-virtual interface. 這個是C++大師們的結論。其原因是因為Derived class在重載Virtual的時候,在很多情況下會影響到基類裡提供的機制的正常運作。如果使用non-public virtual,可以使得程式更加靈活,並且進程pre/post condition的檢查。比如:
class StringArray {
public:
void SortArray ( )
{
// Pre-condition Check
StrategySoryArray();
// Post-condition Check
};
private:
virtual void StrategySoryArray( void ) { ... };
};
這樣通過 pre/post condition check, 可以最大程度的保證基類的使用者不會犯錯誤。從而避免程式bug.
3. 盡量使用 private virtual. Protected virtual 只有在繼承類需要調用基類實現的時候,才應該放在protected裡。比較著名的是clone函數:
class Base {
protected:
Base * clone ( void ) {};
};
class Derived : Base{
protected:
Base * clone ( void ) { Base::Clone(); ... };
};
還有其他的一些方面,那就和基本的class設計大同小異了。不過還有一個重要的方面,就是Operator 和 Copy Constructor.
============== Operator ==============
在Base Class裡邊,所有的 operator中最重要的就是operator =了。對於 Abstract Class 來說,最好是禁止使用 Operator =。 為什麼呢?讓我們來看看代碼:
class Base {
public:
virtual void Interface( void ) = 0;
...
};
class Derived1 : public Base{
private:
_x;
};
class Derived2 : public Base{
private:
_x;
_y;
};
Base *pD1 = new Derived1();
Base *pD2 = new Derived2();
*pD1 = *pD2;
也許 *pD1 = *pD2並不常見,但是確實是合法的代碼,可以通過編譯器的檢查。但是*pD1 = *pD2 調用的是 Base 的 operator = , 在Base 並沒有提供operator =的情況下,使用的是預設的位拷貝操作。這個預設的拷貝操作只拷貝Base Class的記憶體,而忽略了Derived Class的記憶體。也就是說 _x _y都沒有被拷貝。
如果我們在 Base Class 裡提供一個 virtual operator =, 在一定程度上能夠解決問題,但是 overload 的operator 必須進行類型轉換,例如:
Base & Derived1::Operator = (Base & x) {
....
static_cast<Derived1 *>(x)
....
};
但是下面的代碼仍然可以編譯通過,產生錯誤的結果:
*pD1 = *pD2;
因此,對與 abstract class 來說,最方便的就是禁止 operator = , 把它設成 private.
對於Concrete Base Class來說,要麼提供 operator = ,要麼放在private 裡,但是要在Derived Class裡調用 Base Class 的 operator = 在例如:
class Base {
...
public:
Base & operator = ( Base & );
};
class Derived : public Base{
...
public:
Derived & operator = ( Derived & X )
{
Base::operator = ( X );
...
// own assignment operations
}
};
這種方法不支援cast up,也就是:
Base * pBase1 = new Derived();
Base * pBase2 = new Derived();
*pBase1 = *pBase2;
稍微好點的方式是把 Base 的 operator = 放在 protected裡邊,但是也不是解決根本的問題。最好的解決方案是使用前面講到的virtual clone 和 construct實現。如:
class Base {
virtual Base * construct (void) { return new Base(); };
virtual Base * clone(void) { return new Base(*this); };
};
class Derived : public Base {
virtual Base * construct (void)
{ return new Derived(); };
virtual Base * clone(void)
{ return new Derived(*this); };
};
同時禁止Base的 opeartor =。但是這樣的效率會有所降低。總之,最好的方法是使用 abstract interface,同時禁止base使用 private operator =.