【问题标题】:C++: implementing multiple instances of an interface or an optional interface in a classC++:在一个类中实现一个接口或可选接口的多个实例
【发布时间】:2020-07-08 23:58:45
【问题描述】:

我无法找到关于我认为应该是相当常见的问题模式的最佳实践信息。

我将从一个特定的(与软件更新相关的)示例开始,因为它使讨论更加具体,但问题应该是相当笼统的。

假设我有一个软件更新界面:

struct Software_updater {
    virtual ~Software_updater() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

对于我的第一个实现A,我很幸运,一切都很简单。

class A_software_updater : public Software_updater {
    // ...
};

然而,B_software_updater 更复杂。就像在A-case 中一样,它以非平凡的方式连接到目标以进行更新并保持目标连接状态。但更重要的是,它可以更新两个镜像:应用程序镜像和引导加载程序镜像。

就像我目前所拥有的一样,我认为没有真正的理由进行重构,所以我认为我可以在此基础上进行构建。我想出了以下解决方案:

class B_software_updater {
public:
    Software_updater& application_updater() { return application_updater_; }
    Software_updater& boot_loader_updater() { return boot_loader_updater_; }
private:
    class Application_updater : public Software_updater {
        // ...
    } application_updater_;
    class Boot_loader_updater : public Software_updater {
        // ...
    } boot_loader_updater_;
};

即我返回非const 对“接口”成员变量的引用。请注意,它们不能是const,因为它们处于静音状态。

请求 1:我认为上面的解决方案是一个干净的解决方案,但我很乐意得到一些确认

事实上,我最近遇到了一个问题,即必须根据编译时特性的选择,在类中选择性地提供接口,我相信上面的模式也可以解决这个问题:

struct Optional_interface {
    virtual ~Optional_interface() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

class A_implementation {
public:
#ifdef OPTIONAL_FEATURE
    Optional_interface& optional_interface() { return optional_implementation_; }
#endif
    // ...
private:
#ifdef OPTIONAL_FEATURE
    class Optional_implementation : public Optional_interface {
        // ...
    } optional_implementation_;
#endif
    // ...
};

请求 2:我在 A_implementation- 处找不到 simple(如:不是不必要的复杂的基于模板)和简洁的方式来表达编译时可选继承等级。可以吗?

【问题讨论】:

  • 为什么不将optional_implementation_; 设为指向适当接口的唯一指针,并仅在编译时选项打开时填充它?让optional_interface() 返回指针而不是引用,让用户知道他们可能一无所获。一般来说,摆弄接口是个坏主意——它会弄乱 ABI 和通用接口的整个概念。最好只为界面提供选项以返回“无数据”或“不支持此功能”。
  • @ALX23z,好点,但我猜每个硬币都有两个面。当您考虑一致的 ABI 时,作为一名嵌入式开发人员,我正在考虑不要链接不必要的东西。假设 ABI 一致性不是我的应用程序的要求。我的问题实际上是关于将非常量引用(或就此而言的指针,从那个角度来看没有什么不同)返回到“接口”成员变量。由于您在这方面似乎没有问题,因此我会将您的评论视为积极意见。
  • 返回对成员变量的引用并长期使用它们的唯一问题是它们在移动时无效 - 这导致代码对于开发来说很笨重 - 但在您的情况下这可能不是问题.
  • @ALX23z,无论是否适用于我的情况,这仍然是一个非常可怕的问题。你逼我多想,谢谢。我想我刚刚为我的问题找到了更好的解决方案。我可能会将其发布为答案。

标签: c++ inheritance design-patterns interface


【解决方案1】:

更好的解决方案

根据@ALX23z 关于移动时成员变量引用无效的评论,我现在拒绝我的初始解决方案(原始帖子)。这个失效问题对我来说不是问题,但我正在寻找一个通用模式。

像往常一样,一旦找到解决方案,解决方案就很明显了。

首先总结一下我最初的问题。

假设我有一个软件更新界面(或任何界面,这只是一个例子):

struct Software_updater {
    virtual ~Software_updater() = default;
    virtual void action1(const Input1& input1) = 0;
    virtual void action2() = 0;
    virtual bool action3(const Input2& input2) = 0;
    virtual Data1 info1() = 0;
    virtual Data2 info2() = 0;
    // etc.
};

B_software_updater 可以更新两个映像:一个应用程序映像和一个引导加载程序映像。因此,它想提供两个Software_updater接口的实例。

一个比我原来的帖子更好的解决方案是声明一个B_application_updater 和一个B_boot_loader_updater,由B_software_updater& 构造,在B_software_updater 之外,并由客户端代码实例化。

class B_application_updater : public Software_updater {
    B_application_updater(B_software_updater&);
    // ...
};
class B_boot_loader_updater : public Software_updater {
    B_application_updater(B_boot_loader_updater&);
    // ...
};

它确实有强制客户端代码创建三个对象而不是只创建一个的缺点,但我认为清洁度超过了这个缺点。

这也适用于可选接口(参见原始帖子):

class A_optional_implementation : public Optional_interface {
    A_optional_implementation(A_implementation&);
};

A_optional_implementation 将在A_implementation 之外声明。

不需要该接口的应用程序根本不会实例化A_optional_implementation

其他想法

这是适配器设计模式的一个应用!

基本上,这个答案归结为:

  • Interface 类。
  • 一个Implementation 类可以完成这项工作,但并不真正关心接口。它不继承Interface。重点是Implementation 可以“完成”对应于多个接口的工作,而没有多重继承的复杂性和缺点(名称冲突等)。它还可以完成与同一接口的多个实例相对应的工作(我上面的例子)。
  • Interface_adapter 类在其构造函数中采用 Implementation& 参数。它继承了Interface,即它有效地实现了它,这是它的唯一目的。

退后一步,我意识到这只是adapter pattern的一个应用程序(虽然Implementation在这种情况下不一定需要实现任何外部定义的接口——它的接口只是它的公共成员函数)!

中间解决方案:将适配器类留在实现类中

在上面的解决方案中,我指定适配器类是在实现类之外声明的。虽然这对于传统的适配器模式案例来说似乎是合乎逻辑的,但就我而言,我也可以在实现类中声明它们(就像我在原始帖子中所做的那样)并将它们公开。客户端代码仍然需要创建实现和适配器对象,但适配器类将属于实现命名空间,这样看起来会更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多