【发布时间】:2015-10-21 02:52:54
【问题描述】:
我不敢问这个问题,因为它很简单。除非我找不到解决方案。
我最近尝试编写一个简单的程序,该程序在某种程度上忽略了引擎呈现其 UI 的方式。
理论上一切看起来都很棒,但实际上,理论并没有让我走得太远。
假设我的工具需要一个 IWindow 和 IContainer 来承载 ILabel 和 IButton。那是 4 个 UI 元素。去除其中的每一个都是一项微不足道的任务。我可以用 Qt、Gtk、motif 来创建这些元素中的每一个——你可以命名它。
我知道,为了实现(例如,QtWindow 和 QtContainer)工作,抽象(IWindow 和 IContainer)也必须工作:IWindow 需要能够接受IContainer 作为它的孩子:这需要任何一个
- 我可以将任何 UI 元素添加到容器中,或者
- 所有 UI 元素都继承自一个父元素
这是完美解决抽象问题的理论。实践(或实施)是另一回事。为了使实现与抽象一起工作-我认为我可以这样做
- 用暴露实现(或给出提示)的丑陋调用污染抽象——扼杀抽象的概念,或者
- 将抽象的转换添加到实现可以理解的内容 (
dynamic_cast<>())。 - 将
ISomething实例的全局映射池添加到 UI 特定元素 (map<IElement*, QtElement*>()),这有点像强制转换,除了我自己完成。
所有这些看起来都很难看。我在这里看不到其他选择 - 这是数据抽象概念实际上失败的地方吗?投射是这里唯一的选择吗?
编辑
我花了一些时间试图提出最佳解决方案,这似乎是 C++ 无法简单完成的事情。不是没有强制转换,也不是使用模板。
我最终想出的解决方案(在处理了很多接口以及如何定义这些接口之后)如下所示:
1。需要有一个参数化的基本接口来定义调用
基本接口(我们称它为TContainerBase 用于容器,TElementBase 用于元素)指定预期由容器或元素实现的方法。那部分很简单。
定义需要遵循以下思路:
template <typename Parent>
class TElementBase : public Parent {
virtual void DoSomething() = 0;
};
template <typename Parent>
class TContainerBase : public Parent {
virtual void AddElement(TElementBase<Parent>* element) = 0;
};
2。需要有一个指定继承的模板。
这就是分离的第一阶段(引擎与用户界面)来的地方。此时,驱动渲染的后端类型无关紧要。这是有趣的部分:在我看来,唯一成功实现这一点的语言是 Java。模板必须看起来像这样:
一般:
template<typename Engine>
class TContainer : public TContainerBase<Parent> {
void AddElement(TElementBase<Parent>* element) {
// ...
}
};
template<typename Engine>
class TElement : public TElementBase<Parent> {
void DoSomething() {
// ...
}
};
3。 UI 需要能够仅接受 TContainers 或 TElements
也就是说,它必须忽略这些元素的来源。这是分离的第二阶段;毕竟它关心的是TElementBase 和TContainerBase 接口。在 Java 中,通过引入问号已解决。就我而言,我可以简单地在我的 UI 中使用:
TContainer<?> some_container;
TElement<?> some_element;
container.AddElement(&element);
vtable 中的虚函数调用没有问题,因为它们正是编译器所期望的。唯一的问题是确保模板参数在两种情况下都相同。假设后端是一个单一的库 - 这会很好。
上述三个步骤可以让我完全(并且安全地)编写代码而无需考虑后端,而后端几乎可以实现任何需要的东西。
我尝试了这种方法,它变得非常理智。唯一的限制是编译器。实例化类并在这里来回转换它们是违反直觉的,但不幸的是,这是必要的,主要是因为使用模板继承,您不能只提取基类本身,也就是说,您不能说任何一个:
class IContainerBase {};
template <typename Parent>
class TContainerBase : public (IContainerBase : public Parent) {}
也没有
class IContainerBase {};
template <typename Parent>
typedef class IContainerBase : public Parent TContainerBase;
(请注意,在上述所有解决方案中,感觉完全自然而理智地依赖 TElementBase 和 TContainerBase - 如果您将 TElementBase<Foo> 转换为TElementBase<Bar> - 所以这只是语言限制)。
无论如何,这些最终语句(类 A 的 typedef 继承自 B 和类 X 的基类 A 继承自 B)在 C++ 中只是垃圾(并且会使语言变得比现在更难),因此唯一的出路是遵循提供的解决方案之一,我非常感谢。
感谢大家的帮助。
【问题讨论】:
-
尝试从程序的 UI 代码中抽象出程序的逻辑。这样数据表就不会直接呈现给 UI。相反,它包含某种抽象组件,它可以做到这一点。该组件可以在逻辑不关心的地方针对不同的 UI 引擎进行定制。
-
如果
QtWindow实现IWindow和GtkContainer实现IContainer,是什么阻止我添加GtkContainer作为QtWindow的子代?我认为这是您问题的根源:您已经从 彼此 中抽象出不同类型的 UI 组件,而不仅仅是从应用程序中。 -
对上面的一些澄清:我正在努力解决的问题是
IContainer和IElement之间的边界,换句话说,我可以将方法:IContainer::Add(IElement*)添加到抽象中,但是——实施呢?显然,实现也将接收IElement*,然后它必须强制转换,否则IElement将需要导出一个自定义结构,以便UI 实现可以理解。难道只有这两种方式吗?这就是我失败的地方...... -
是的,您需要在胶水层中进行铸模。每个 UI 元素类一次转换(或仅一次转换,如果您将其模板化)几乎不会造成灾难。
标签: c++ abstraction