您的位置:首页 > 编程语言 > C语言/C++

C++基本功和 Design Pattern系列 Base Class Design

2007-05-30 15:03 316 查看
在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 =.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: