我很难找到有关我认为应该是相当普遍的问题模式的最佳实践信息。
我将从一个特定的(与软件更新相关的)示例开始,因为它使讨论更加具体,但是问题应该是相当普遍的。
说我有一个软件更新程序界面:
class 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 {
// ...
};
A B_software_updater
然而,更为复杂。像在A
情况下一样,它以非平凡的方式连接到目标以进行更新,并保持目标连接状态。但更重要的是,它可以更新两个映像:应用程序映像和引导加载程序映像。
喜欢到目前为止,我没有真正的理由进行重构,所以我认为我可以在此基础上继续前进。我提出以下解决方案:
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:我认为上面的解决方案很干净,但是我很乐意得到一些确认。
实际上,我最近遇到了必须根据功能的编译时选择在类中提供接口的问题,并且我相信上面的模式也可以解决该问题:
class 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
级别的编译时可选继承。可以吗?
基于@ ALX23z关于移动成员变量引用无效的评论,我现在拒绝我的最初解决方案(原始帖子)。对于我的案例来说,无效问题不是问题,但我正在寻找一种通用模式。
和往常一样,一旦找到解决方案就很明显。
首先是我最初的问题的摘要。
说我有一个软件更新程序界面(或任何界面,这只是一个示例):
class 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
不一定需要实现任何外部定义的接口-其接口只是其公共成员函数!)!] >
在上面的解决方案中,我指定适配器类在实现类的外部声明。尽管这对于传统的适配器模式案例来说似乎是合乎逻辑的,但就我而言,我也可以在实现类中声明它们(就像我在原始帖子中所做的那样)并将它们公开。客户端代码仍然必须创建实现和适配器对象,但是适配器类将属于实现名称空间,这看起来会更好。