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