【问题标题】:C++: How to get decoupled polymorphic behaviorC++:如何获得解耦的多态行为
【发布时间】:2013-03-30 18:09:44
【问题描述】:

在 C++ 中的 Qt 项目中,我正在使用 QtPlugin 为动态加载的插件编写接口。这个界面应该允许插件注册它们的不同参数,并且在加载插件时,主程序应该显示代表每个参数的适当的 GUI 控件。例如,参数可以是 0 到 20 a 之间的 int,由 QLabel 和 QSlider 表示,也可以是由 QColorDialog 表示的颜色值。

这里有一个问题:我尝试了一种标准的 OOP 方法(?),让每个参数类型都继承一个抽象类并通过实现一个虚函数来创建 GUI 表示。这导致大量 Qt GUI 头文件链接到每个插件文件,将其大小从 ~20 KB 增加到 ~50 KB。

这不是为了节省这些千字节,而是为了更好地理解 OOP。我考虑了这一点并试图找到合适的设计模式,然后我用谷歌搜索了“解耦多态”、“外部多态”等,并遇到了一个页面,上面说这是可能的,但通常你不想去那里,因为 它破坏了 OOP
是这样吗?要么我从插件界面隐藏 GUI 代码,并用枚举或其他东西识别每种类型,然后“打破 OOP”,还是这个类完全对自己负责,但在内部也完全耦合?

如果每种参数类型都由数据模型、持久性和带有信号的 GUI 控件组成,您会推荐哪些解决方案?什么去哪里?

编辑:换句话说,我想知道插件是否可以是纯数据和算法,不知道如何在 Qt 中创建数据控件并且独立于 Qt GUI 标头。不过它可能会使用 Q_OBJECT 作为信号。

【问题讨论】:

  • 您的拼写错误可能会损害您的 Google 搜索结果。 :)
  • 我只是在用错误的语言思考了一会儿。不,谷歌会为我纠正这一点......编辑:嗯,它在 3 个地方,必须小心!谢谢。 :)

标签: c++ qt design-patterns plugins decoupling


【解决方案1】:

我建议让插件担心其参数的类型,并有一个单独的组件知道如何将每种类型映射到 GUI 控件上。

这几乎是一个直接的模型/视图分解,所以似乎是一个很好理解的习语。

现在,您的 type 模型可以被枚举,或者您可以使用可以说是更多的 OO 访问者模式,但您基本上仍然在提出一个固定且不可扩展的类型系统的时间。够吗?

您最终可能会得到某种类型,该类型既知道给定参数的特定派生类型,又知道如何在 Qt 中呈现它的细节。这将处理 Qt 信号,并将值传回参数。


...通过尝试 dynamic_cast 或读取某种识别代码(例如枚举),我在想。我仍然不明白如何使用访客 DP 来代替这些......

访问者模式专门用于避免dynamic_cast,所以我不确定这里有什么混淆。诚然,有一个使用 dynamic_cast 的 post-hoc 版本,但它在实现中被隐藏起来,无论如何都不是常见的情况。

因此,举个具体的例子,让我们创建一个具有几个参数类型的模型:

struct ArgumentHandler; // visitor
class Argument { // base class for visitable concrete types
public:
    virtual void visit(ArgumentHandler&) = 0;
};
// sample concrete types
class IntegerArgument: public Argument {
    int value_;
public:
    IntegerArgument(int value = 0) : value_(value) {}

    void set(int v) { value_ = v; }
    int get() const { return value_; }

    virtual void visit(ArgumentHandler&);
};
class BoundedIntegerArgument: public IntegerArgument
{
    int min_, max_;
public:
    virtual void visit(ArgumentHandler&);
    // etc...
};

现在我们有了一些具体的类型供它访问,我们可以写抽象的访问者

struct ArgumentHandler {
    virtual ~ArgumentHandler() {}

    virtual void handleInteger(IntegerArgument&);
    virtual void handleBoundedInteger(BoundedIntegerArgument&);
    // ...
};

我们的具体类型像这样实现访问:

void IntegerArgument::visit(ArgumentHandler& handler) {
    hander.handleInteger(*this);
}

void BoundedIntegerArgument::visit(ArgumentHandler& handler) {
    hander.handleBoundedInteger(*this);
}

现在,我们可以只根据数据模型类型编写一个抽象插件——它不需要了解有关 GUI 工具包的任何信息。假设我们现在只提供一种查询其参数的方法(注意每个具体的子类型都应该有 set/get 方法)

class PluginBase
{
public:
    virtual int arg_count() const =  0;
    virtual Argument& arg(int n) =  0;
};

最后,我们可以绘制一个视图,它知道如何查询抽象插件的参数、如何显示每个具体的参数类型以及如何处理输入:

// concrete renderer
class QtView: public ArgumentHandler
{
    struct Control {};
    struct IntegerSpinBox: public Control {
        QSpinBox control_;
        IntegerArgument &model_;
    };
    struct IntegerSlider: public Control {
        QSlider control_;
        BoundedIntegerArgument &model_;
    };
    std::vector<std::unique_ptr<Control>> controls_;
public:
    // these overloads know how to render each argument type
    virtual void handleInteger(IntegerArgument &arg) {
        controls_.push_back(new IntegerSpinBox(arg));
    }
    virtual void handleBoundedInteger(BoundedIntegerArgument &arg) {
        controls_.push_back(new IntegerSlider(arg));
    }
    // and this is how we invoke them:
    explicit QtView(PluginBase &plugin) {
        for (int i=0; i < plugin.arg_count(); ++i) {
            plugin.arg(i).visit(*this);
        }
    }
};

我省略了所有虚拟析构函数、Qt 信号处理等等。但是,希望您能看到 QtView::IntegerSpinBox 对象如何处理来自其专属 spinbox 小部件的 valueChanged 信号,并调用 model_.set() 将其推送回插件。

【讨论】:

  • 是的,固定类型系统在这里不是问题。我确实考虑过访客 DP,但认为虽然整洁,但不会改变链接情况。确实是MVC的一个案例,难道MVC的重点不就是数据和视图分离吗?那为什么所有的解决方案和DP似乎都给了我一个最终知道如何渲染自己的类型?枚举类型模型和远程条件是迄今为止最接近的,因为我希望插件是隔离的数据和逻辑。我很好奇为什么想要这样做似乎如此不寻常!
  • 重点是您可以避免将渲染依赖项(即用于显示特定抽象类型的 QWhatever 类型)下推到模型中。您仍然有一些组件知道抽象类型以及如何显示它们,但您将其与模型(在本例中为您的插件)分开。
  • 我明白了,对不起,我误解了你。这是有道理的,但有点回到最初的问题:这个其他组件如何知道模型的具体类型?通过尝试 dynamic_cast 或阅读某种识别码(例如枚举),我在想。正如您似乎建议的那样,我仍然看不到如何代替使用访问者 DP。您介意用一些示例类更新您的答案吗?
  • 现在我更好地了解了它的使用方式!我之前看到的它的例子没有抽象的Visitor,所以控制是倒置的,但并没有真正解耦。我想我完全错过了这个 DP 的重点。非常感谢!
【解决方案2】:

您可以将任何类型的消息发送到任何地方,并使用 templatious virtual packs 的任何内容在另一端捕获它,这正是为了与任何事物进行松散耦合而制作的。

【讨论】:

    【解决方案3】:

    如果我对您的理解正确,您应该重新考虑这种行为。您可以为模块特定的渲染器创建一个基类,并在每个模块中创建一个工厂,以实例化模块的具体渲染器,而不是让模块在主应用程序中注册所有内容(实际上可能很多)。然后,您可以要求模块呈现您提供给模块的信息。

    【讨论】:

    • 也许“注册”不是正确的表达方式。我的意思是,模块只需要知道它使用了哪些数据,就接口中的可用类型而言,而不是每种数据类型在 GUI 中的呈现方式。至少我看不到在模块中为特定渲染器类创建此类工厂的方法,而无需将基本的 Qt GUI 标头链接到每个模块文件中。除非有某种“现在请为我实例化适当的具体渲染器,它是在主程序的某处而不是这里定义的”。
    猜你喜欢
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 2020-05-13
    • 2015-08-25
    • 1970-01-01
    • 2017-02-11
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多