C ++:如何获得解耦的多态行为

时间:2013-03-30 18:09:44

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

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

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

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

如果每种参数类型都包含数据模型,持久性和带信号的GUI控件,那么您会推荐哪些解决方案?怎么回事?

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

3 个答案:

答案 0 :(得分:3)

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

这几乎是一个直接的模型/视图分解,所以看起来像一个易于理解的习语。

现在,你的类型模型可以被枚举,或者你可以使用可以说更多的OO访问者模式,但你仍然在提出一个固定的,不是真正可扩展的类型系统时间那够了吗?

你可能最终得到一些知道给定参数的特定派生类型的类型,以及如何在Qt中呈现它的细节。这将处理Qt信号,并将值传递回参数。


  

...通过尝试动态广播或阅读某种识别码,例如枚举,我在想。我仍然没有看到如何使用Visitor DP代替这些......

访客模式具体用于避免dynamic_cast,所以我不确定这里有什么混乱。不可否认,有一个使用dynamic_cast的事后版本,但是它隐藏在实现中并且通常不是常见的情况。

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

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;
};

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

// 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对象如何处理来自其捕获的旋转框小部件的valueChanged信号,并调用model_.set()将其推回到插件。

答案 1 :(得分:1)

您可以向任何地方发送任何类型的消息,并在另一侧捕获templatious virtual packs的任何内容,这些内容完全是为了松散耦合而实现的。

答案 2 :(得分:0)

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