在C ++中设计ABC(抽象基类)的好习惯

时间:2012-03-06 17:26:50

标签: c++ design-patterns abstract-class

在java中,我们可以定义不同的接口,然后我们可以为具体的类实现多个接口。

// Simulate Java Interface in C++
/*
interface IOne {
    void   MethodOne(int i);
    .... more functions
}

interface ITwo {
    double MethodTwo();
    ... more functions
}

class ABC implements IOne, ITwo {
    // implement MethodOne and MethodTwo
}
*/

在C ++中,一般来说,我们应该避免使用多重继承,尽管多继承确实在某些情况下具有优势。

class ABC {
public:
    virtual void   MethodOne(int /*i*/) = 0 {}
    virtual double MethodTwo() = 0 {}

    virtual ~ABC() = 0 {}

protected:
    ABC() {} // ONLY ABC or subclass can access it
};

问题1 >基于ABC的设计,我应该改进其他任何事情,以使其成为一个体面的ABC吗?

问题2 >良好的ABC不应该包含成员变量,而变量应该保存在子类中吗?

问题3 >正如我在评论中指出的那样,如果ABC必须包含太多纯函数,该怎么办?还有更好的方法吗?

4 个答案:

答案 0 :(得分:9)

  

在C ++中,一般来说,我们应该避免使用多重继承

与任何其他语言功能一样,您应该在适当的地方使用多重继承。接口通常被认为是多重继承的适当使用(例如,参见COM)。

ABC的构造函数不需要保护 - 它不能直接构造,因为它是抽象的。

不应将ABC析构函数声明为纯虚拟(当然,它应声明为虚拟)。如果不需要派生类,则不应要求派生类实现用户声明的构造函数。

接口不应该有任何状态,因此不应该有任何成员变量,因为接口只定义了如何使用某些东西,而不是如何实现它。

ABC永远不应该有太多的成员函数;它应该具有所需的数量。如果太多,您显然应该删除那些未使用或不需要的,或者将接口重构为几个更具体的接口。

答案 1 :(得分:9)

  1. 除非必要,否则不要为纯虚方法提供实现。
  2. 不要让你的析构函数纯粹虚拟。
  3. 不要让您的构造函数受到保护。您无法创建抽象类的实例。
  4. 最好在源文件中隐藏构造函数和析构函数的实现,以免污染其他目标文件。
  5. 使您的界面不可复制。
  6. 如果这是一个界面,那么最好没有任何变量。否则它将是一个抽象基类而不是接口。

    太多的纯函数都可以,除非你能用较少的纯函数来完成它。

答案 2 :(得分:4)

  

根据ABC的设计,我应该改进其他任何东西,以使其成为一个体面的ABC吗?

你有几个语法错误。出于某种原因,您不允许在类定义中放置纯虚函数的定义;无论如何,你几乎肯定不想在ABC中定义它们。所以声明通常是:

virtual void MethodOne(int /*i*/) = 0;   // ";" not "{}" - just a declaration

将析构函数设置为纯粹没有任何意义,尽管它应该是虚拟的(或者,在某些情况下,非虚拟和受保护 - 但最安全的是使它成为虚拟的)。

virtual ~ABC() {}  // no "= 0"

不需要受保护的构造函数 - 它是抽象的事实已经阻止了实例化,除了作为基类。

  

一个好的ABC不应该包含成员变量,而变量应该保存在子类中吗?

通常,是的。这样可以在界面和实现之间实现清晰的分离。

  

正如我在评论中指出的那样,如果ABC必须包含太多纯函数,该怎么办?还有更好的方法吗?

界面应该尽可能复杂,而不是更多。只有一些是不必要的,只有“太多”的功能;在这种情况下,摆脱它们。如果界面看起来太复杂,它可能会尝试做多件事;在这种情况下,您应该能够将其分解为更小的接口,每个接口都有一个目的。

答案 3 :(得分:3)

首先:为什么我们应该避免C ++中的多重继承?我从来没见过 一个大的应用程序,没有广泛使用它。继承自 多个接口是使用它的好例子。

请注意,只要您想使用,Java的interface就会被破坏 通过合同编程,你坚持使用抽象类,并且 他们不允许多重继承。但是,在C ++中,它很简单:

class One : boost::noncopyable
{
    virtual void doFunctionOne( int i ) = 0;
public:
    virtual ~One() {}
    void functionOne( int i )
    {
        //  assert pre-conditions...
        doFunctionOne( i );
        //  assert post-conditions...
    }
};

class Two : boost::noncopyable
{
    virtual double doFunctionTwo() = 0;
public:
    virtual ~Two() {}
    double functionTwo()
    {
        //  assert pre-conditions...
        double results = doFunctionTwo();
        //  assert post-conditions...
        return results;
    }
};

class ImplementsOneAndTwo : public One, public Two
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

或者,你可以有一个复合界面:

class OneAndTwo : public One, public Two
{
};

class ImplementsOneAndTwo : public OneAndTwo
{
    virtual void doFunctionOne( int i );
    virtual double doFunctionTwo();
public:
};

并从中继承,这是最有意义的。

这或多或少是标准的习语;在没有的情况下 可以想象是界面中的任何前置或后置条件(通常是 调用反转),虚函数可能是公共的,但一般来说, 他们将是私人的,因此你可以强制执行预先和 后置条件。

最后,请注意在很多情况下(特别是如果上课 代表一个值),你将直接实现它,而不是 接口。与Java不同,您不需要单独的界面来维护 在类的不同文件中实现 定义 - 这是C ++默认工作的方式(使用类 标题中的定义,但源文件中的实现代码。)