【问题标题】:Name of this C++ pattern and the reasoning behind it?此 C++ 模式的名称及其背后的原因?
【发布时间】:2014-05-04 05:59:21
【问题描述】:

在我公司的 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]:我一般理解抽象和继承的意义,但不理解这种特殊的继承用法

【问题讨论】:

  • 这不是真正的设计模式,而是核心 OOP 概念
  • 你怎么知道以后没有派生其他类的打算?即使作者本人无意从FooApi 派生,他也为其他人提供了从中派生的选项。或者,如果他后来决定他确实想要这样做,那么他自己也可以选择。
  • 因为它在整个大型代码库中被重复使用,并且所有抽象类只有一个派生类,并且它们都以派生类命名。 IE。有 Foo、FooApi、Bar、BarApi、Quxx、QuxxApi 等类。代码的其他部分正常使用继承,不遵循此命名方案。
  • 这种技术的名称是interface

标签: c++ design-patterns inheritance


【解决方案1】:

我能看出他们这样做的唯一原因是出于封装的目的。这里的重点是代码库中的大多数其他代码only 需要包含“FooApi.h”/“BarApi.h”/“QuxxApi.h”标头。只有创建 Foo 对象的代码部分实际上需要包含“Foo.h”标头(并与包含类函数定义的对象文件链接)。对于单例,通常创建 Foo 对象的唯一位置是在“Foo.cpp”文件中(例如,作为 Foo 类的静态成员函数中的局部静态变量,或类似的东西)。

这类似于使用前向声明来避免包含包含实际类声明的标头。但是在使用前向声明时,您仍然需要最终包含标头才能调用任何成员函数。但是当使用这种“抽象 + 实际”的类模式时,您甚至不需要包含“Foo.h”标头即可调用 FooApi 的成员函数。

换句话说,这种模式对 Foo 类的实现(和完整的声明)提供了非常强大的封装。您获得的好处与使用Compiler Firewall idiom 大致相同。 Here 是关于这些问题的另一个有趣的读物。

我不知道那个模式的名字。与我刚才提到的其他两种模式(编译器防火墙和前向声明)相比,它并不是很常见。这可能是因为这种方法的运行时开销比其他两种方法要多。

【讨论】:

  • FooApi 是 Java 等人的。将称为“接口”。您的回答提供了可靠的 C++ 推理,为什么即使接口只有一个实现者,分离接口和实现也是有用的。
【解决方案2】:

这是为了以后添加代码。假设NewFoo 也扩展/实现了FooApi。所有当前的基础架构都可以与FooNewFoo 一起使用。

【讨论】:

    【解决方案3】:

    这样做的原因可能与使用 pImpl(“指向实现习惯用法的指针”,有时称为“私有实现习惯用法”)的原因相同 - 将私有实现细节保留在标头之外,这意味着通用构建系统像使用文件时间戳来触发代码重新编译的 make 将不会在仅更改实现时重建客户端代码。相反,包含新实现的对象可以链接到现有的客户端对象,实际上如果实现分布在共享对象(也称为动态链接库/DLL)中,客户端应用程序可以在下一个获取更改的实现库它运行的时间(或者如果它在运行时链接,则执行dlopen() 或等效项)。除了促进更新实施的分发之外,它还可以减少重建时间,从而实现更快的编辑/测试/编辑/...周期。

    这样做的代价是必须通过离线虚拟调度访问实现,因此会影响性能。这通常是微不足道的,但如果像 get-int-member 这样的微不足道的函数在性能关键循环中被调用数百万次,它可能会引起人们的兴趣 - 每次调用都可能比内联成员访问慢一个数量级。

    它的“名称”是什么?好吧,如果您说您使用的是“界面”,那么大多数人都会明白这一点。这个术语在 C++ 中有点含糊,因为有些人在基类具有虚方法时使用它,而另一些人则期望基类是抽象的,缺少数据成员和/或私有成员函数和/或函数定义(除了虚拟析构函数)。对“接口”一词的期望有时——无论好坏——受到 Java 语言关键字的影响,该关键字将接口类限制为抽象的,不包含静态方法或函数定义,所有函数都是公共的,并且只有 const@ 987654323@数据成员。

    著名的四种设计模式帮中没有一个与您引用的用法相对应,尽管无疑很多人已经发布(网络或其他方式)相应的“模式”,但它们的使用可能不够广泛(与相同的意思!)比“界面”更容易混淆。

    【讨论】:

      【解决方案4】:

      FooApi 是一个虚拟基类,它为具体实现提供接口(@98​​7654322@)。

      关键是您可以根据FooApi 实现功能,并创建满足其接口并且仍然可以使用您的功能的多个实现。当您有多个后代时,您会看到一些优势 - 该功能可以与多个实现一起使用。一个人可能会实现不同类型的 Foo 或用于不同的平台。

      重新阅读我的答案,我认为我不应该再谈论 OO。

      【讨论】:

      • virtual 关键字不是必需的;无论关键字是否存在,这些方法都是虚拟的。
      • @ValekHalfHeart 在 C++ 中,成员函数默认是非虚拟的。
      • 如果我尝试声明没有虚拟的纯函数,我会收到错误。
      • @JoshTownzen ...除非他们重写了虚拟方法。
      • @ValekHalfHeart 啊。原始帖子在基类中没有 virtual 函数。在 Rhythmic Fistman 提到它们丢失后,这些被编辑了。
      猜你喜欢
      • 2011-12-11
      • 2019-12-24
      • 1970-01-01
      • 1970-01-01
      • 2018-10-25
      • 2014-06-06
      • 1970-01-01
      • 2018-06-07
      • 1970-01-01
      相关资源
      最近更新 更多