真正允许在 C++ 中将实现与用户隔离的数据抽象

Data abstraction that really allows isolating implementation from the user in C++

我犹豫要不要问这个问题,因为它看似简单。除了我没有看到解决方案。

我最近尝试编写一个简单的程序,该程序在某种程度上忽略了引擎呈现其 UI 的内容。

一切在纸面上看起来都很棒,但实际上,理论并没有让我走得太远。

假设我的工具需要一个 IWindowIContainer 来托管 ILabelIButton。那是 4 UI 个元素。抽象其中的每一个是一项微不足道的任务。我可以使用 Qt、Gtk、motif 创建这些元素中的每一个 - 随你便。

我明白为了实现(比如,QtWindowQtContainer)工作,抽象(IWindowIContainer)必须工作,太:IWindow 需要能够接受 IContainer 作为它的 child:那个 requires 或者那个

这就是完美解决抽象问题的理论。实践(或实施)是另一回事。为了使实现与抽象一起工作——按照我的看法,我可以

所有这些看起来都很丑。我在这里看不到其他选择——这是数据抽象概念实际上失败的地方吗?铸造是这里唯一的选择吗?

编辑

我花了一些时间试图提出最佳解决方案,这似乎是用 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。需要有一个指定继承的模板。

这就是分离的第一阶段(引擎 vs ui)。此时,驱动渲染的后端类型无关紧要。这是有趣的部分:正如我所想,唯一成功实现它的语言是 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 需要能够接受 TContainersTElements

也就是说,它必须忽略这些元素的来源。那是分离的第二阶段;毕竟它关心的是 TElementBaseTContainerBase 接口。在 Java 中,通过引入问号解决了这个问题。就我而言,我可以简单地在 UI:

中使用
TContainer<?> some_container;
TElement<?> some_element;
container.AddElement(&element);

vtable 中的虚函数调用没有问题,因为它们正是编译器所期望的位置。唯一的问题是确保两种情况下的模板参数相同。假设后端是一个单一的库 - 就可以正常工作。

以上三个步骤可以让我在编写代码时完全(安全地)不考虑后端,而后端可以实现任何需要的东西。

我尝试了这种方法,结果证明它非常明智。唯一的限制是编译器。实例化 class 并在此处来回转换它们是 counter-intuitive,但不幸的是,这是必要的,主要是因为使用模板继承,您不能仅提取基础 class 本身,也就是说,您不能说:

class IContainerBase {};

template <typename Parent>
class TContainerBase : public (IContainerBase : public Parent) {}

也不

class IContainerBase {};
template <typename Parent>
typedef class IContainerBase : public Parent TContainerBase;

(请注意,在上述所有解决方案中,感觉 完全自然而理智,仅依赖于 TElementBaseTContainerBase - 并且生成的代码有效如果将 TElementBase<Foo> 转换为 TElementBase<Bar> 就完全没问题了——所以这只是语言限制)。

无论如何,这些最终语句(class A 继承自 B 的类型定义和 class X 具有基数 class A 继承自 B)在 C++ 中只是垃圾(并且会使语言比现在更难),因此唯一的出路是遵循提供的解决方案之一,对此我深表感谢。

感谢大家的帮助。

如果我对你的问题的理解正确,这就是 Abstract Factory Pattern 旨在解决的情况。

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part of the theme. The client doesn't know (or care) which concrete objects it gets from each of these internal factories, since it uses only the generic interfaces of their products. This pattern separates the details of implementation of a set of objects from their general usage and relies on object composition, as object creation is implemented in methods exposed in the factory interface.

创建一个能够抽象库(如 Qt 和 Gtk)的包装器对我来说似乎不是一项微不足道的任务。但是更一般地谈论您的设计问题,也许您可​​以使用模板来进行抽象接口和特定实现之间的映射。例如:

抽象接口IWidget.h

template<typename BackendT>
class IWidget
{
public:
    void doSomething()
    {
        backend.doSomething();
    }

private:
    BackendT backend;
};

Qt 实现QtWidget.h:

class QtWidget
{
public:
    void doSomething()
    {
        // qt specifics here
        cout << "qt widget" << endl;
    }
};

Gtk 实现 GtkWidget.h:

class GtkWidget
{
public:
    void doSomething()
    {
        // gtk specifics here
        cout << "gtk widget" << endl;
    }
};

Qt 后端 QtBackend.h:

#include "QtWidget.h"
// include all the other gtk classes you implemented...
#include "IWidget.h"

typedef IWidget<QtWidget> Widget;
// map all the other classes...

Gtk 后端 GtkBackend.h:

#include "GtkWidget.h"
// include all the other gtk classes you implemented...
#include "IWidget.h"

typedef IWidget<GtkWidget> Widget;
// map all the other classes...

申请:

// Choose the backend here:
#include "QtBackend.h"

int main()
{
    Widget* w = new Widget();
    w->doSomething();

    return 0;
}

您正在尝试在此处使用面向对象。 OO 有一种实现通用代码的特殊方法:通过类型擦除。 IWindow 基础 class 接口擦除确切的类型,在您的示例中将是 QtWindow。在 C++ 中,您可以通过 RTTI 取回一些已擦除的类型信息,即 dynamic_cast.

但是,在 C++ 中,您也可以使用模板。不要实现 IWindowQtWindow,而是实现 Window<Qt>。这允许您声明 Container<Foo> 接受任何可能的 Foo window 库的 Window<Foo>。编译器将强制执行此操作。