用C++设计ABC(抽象基类)的良好实践
Good practice to design a ABC(Abstract Base Class) in C++
在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
必须包含太多纯函数,该怎么办?有更好的方法吗?
- 除非必要,否则不要为纯虚拟方法提供实现
- 不要让你的析构函数纯粹是虚拟的
- 不要让你的构造函数受到保护。不能创建抽象类的实例
- 最好将构造函数和析构函数的实现隐藏在源文件中,以免污染其他对象文件
- 使您的界面不可复制
如果这是一个接口,最好不要有任何变量。否则,它将是一个抽象基类,而不是一个接口。
太多的纯函数是可以的,除非你可以用不太纯的函数。
在C++中,一般来说,我们应该避免使用多重继承
与任何其他语言功能一样,您应该在适当的地方使用多重继承。接口通常被认为是对多重继承的适当使用(例如,请参阅COM)。
ABC
的构造函数不需要保护——因为它是抽象的,所以不能直接构造它。
ABC
析构函数不应该声明为纯虚拟的(当然,它应该声明为虚拟的)。如果派生类不需要用户声明的构造函数,则不应要求它们实现该构造函数。
接口不应该有任何状态,因此不应该有任意成员变量,因为接口只定义了如何使用某个东西,而不是如何实现它。
ABC
不应具有过多的成员函数;它应该具有所需的确切数字。如果太多,显然应该删除那些没有使用或不需要的,或者将接口重构为几个更具体的接口。
根据ABC的设计,我是否应该改进其他方面,使其成为一个像样的ABC?
您有几个语法错误。出于某种原因,不允许将纯虚拟函数的定义放在类定义中;在任何情况下,你几乎肯定不想在ABC中定义它们。因此,声明通常是:
virtual void MethodOne(int /*i*/) = 0; // ";" not "{}" - just a declaration
尽管析构函数应该是虚拟的(或者,在某些情况下,是非虚拟的和受保护的,但使其成为虚拟的是最安全的),但使它成为纯析构函数并没有任何意义。
virtual ~ABC() {} // no "= 0"
不需要受保护的构造函数——事实上,它是抽象的,除了作为基类之外,已经阻止了实例化。
一个好的ABC不应该包含成员变量,而是应该将变量保存在子类中,这是真的吗?
通常,是的。这在接口和实现之间提供了一个清晰的分离。
正如我在评论中指出的,如果ABC必须包含太多纯函数,该怎么办?有更好的方法吗?
接口应该像它需要的那样复杂,而不是更多。如果有些功能是不必要的,那么只有"太多"的功能;在这种情况下,把他们赶走。如果界面看起来太复杂,它可能会尝试做不止一件事;在这种情况下,您应该能够将其分解为更小的接口,每个接口都有一个目的。
第一:为什么我们要避免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++默认情况下的工作方式(使用类头中的定义,但源文件中的实现代码)。
- 类设计:类成员继承自同一基类
- 在这种情况下,如何在基类中设计开关大小写函数
- 通过基指针C++设计模式删除派生类
- 使用基类指针 - cpp 的适当声明设计接口
- 如何设计基类,使其在运行时知道所有"derived"类?
- 为具有不同签名的函数的派生类设计基类
- C 类设计 - 如何在基类驱动器中清理
- 基类c++的子类的通用设计接口
- c++设计:从基类强制转换为派生类,没有额外的数据成员
- 设计问题迫使我将基类向下转换为派生类型
- 类层次结构设计,避免从基类到派生类的向下转换
- 设计问题,基类知道它的导数
- 用C++设计ABC(抽象基类)的良好实践
- Qt设计器:更改窗口的基类
- 基类函数调用的替代设计模式
- 模板实例化的抽象基类的子类的构造函数-需要替代设计模式
- 具有继承的桥接设计模式,其中抽象基类具有成员数据
- c++设计(基类中的行为,派生类中提供的私有成员)
- 类设计以避免需要基类列表
- 为基类设计基迭代器