在我公司的C ++代码库中,我看到很多类定义如下:
// FooApi.h
class FooApi {
public:
virtual void someFunction() = 0;
virtual void someOtherFunction() = 0;
// etc.
};
// Foo.h
class Foo : public FooApi {
public:
virtual void someFunction();
virtual void someOtherFunction();
};
Foo
是唯一一个继承自FooApi
的类,而使用Foo
对象获取或返回指针的函数则使用FooApi *
。它似乎主要用于单身类。
这是编写C ++代码的常用命名方法吗?它有什么意义呢?我不知道如何使用一个单独的,纯粹的抽象类来定义类的接口是有用的。
编辑[0] :很抱歉,为了澄清,只有一个来自FooApi
的课程,并且无意在以后添加其他课程。
编辑[1] :我理解一般的抽象和继承,但不是继承的这种特殊用法。
答案 0 :(得分:3)
我可以理解他们为什么会这样做的唯一原因是封装目的。这里的要点是代码库中的大多数其他代码只需要包含" FooApi.h" /" BarApi.h" /" QuxxApi.h"头。只有创建 Foo对象的代码部分实际上需要包含" Foo.h" header(以及包含类'函数定义的object-file的链接)。对于单身人士来说,通常创建Foo对象的唯一地方是" Foo.cpp" file(例如,作为Foo类的静态成员函数中的本地静态变量,或类似的东西)。
这与使用forward-declarations类似,以避免包含包含实际类声明的标头。但是在使用前向声明时,您仍然需要最终包含标头,以便能够调用任何成员函数。但是当使用这个"抽象+实际"课堂模式,你甚至不需要包括" Foo.h"标题,以便能够调用FooApi的成员函数。
换句话说,这种模式为Foo类提供了非常强大的封装功能。实施(和完整的声明)。您可以获得与使用Compiler Firewall idiom大致相同的好处。 Here是关于这些问题的另一个有趣的读物。
我不知道那种模式的名称。与我刚刚提到的其他两种模式(编译器防火墙和前向声明)相比,它并不常见。这可能是因为此方法比其他两种方法具有更多的运行时开销。
答案 1 :(得分:1)
这是为了稍后添加代码。让我们说NewFoo
也扩展/实现FooApi
。所有当前的基础架构都适用于Foo
和NewFoo
。
答案 2 :(得分:1)
这可能是因为使用了pImpl(“指向实现习惯用户指针”,有时称为“私有实现习惯用法”)的原因 - 将私有实现细节保留在标头之外,这意味着常见的构建系统就像make使用文件时间戳来触发代码重新编译一样,只有在实现发生变化时才会重建客户端代码。相反,包含新实现的对象可以链接到现有的客户端对象,实际上如果实现分布在共享对象(也称为动态链接库/ DLL)中,客户端应用程序可以在下一个接收更改的实现库它运行的时间(如果它在运行时链接,则执行dlopen()
或等效)。除了促进更新实施的分发外,它还可以减少重建时间,从而实现更快的编辑/测试/编辑/ ...循环。
这样做的代价是必须通过外线虚拟调度来访问实现,因此会影响性能。这通常是无关紧要的,但如果在性能关键循环中调用像get-int-member这样的普通函数数百万次,那么它可能很有意义 - 每次调用都可能比内联成员访问慢一个数量级。
它的“名称”是什么?好吧,如果你说你正在使用“界面”,大多数人都会得到一般的想法。这个术语在C ++中有点模糊,因为有些人只要基类有虚方法就会使用它,其他人则希望基类是抽象的,缺少数据成员和/或私有成员函数和/或函数定义(虚拟析构函数除外) )。围绕术语“接口”的期望有时 - 无论好坏 - 受Java语言关键字的影响,这限制了接口类是抽象的,不包含静态方法或函数定义,所有函数都是公共的,只有{{1} } const
数据成员。
众所周知的Gang of Four Design Patterns中没有一个符合您引用的用法,虽然很多人发布了(网络或其他)相应的“模式”,但它们的使用可能还不够广泛(同样的意思!)比“接口”更容易混淆。
答案 3 :(得分:0)
FooApi
是一个虚拟基类,它为具体实现提供了接口(Foo
)。
关键是你可以用FooApi
来实现功能,并创建满足其接口的多个实现,并且仍然可以使用你的功能。当您有多个后代时,您会看到一些优势 - 该功能可以与多个实现一起使用。可以实现不同类型的Foo或不同的平台。
重新阅读我的回答,我不认为我应该再次谈论OO。