В проекте Qt на C ++ я пишу интерфейс для динамически загружаемых плагинов, используя QtPlugin. Этот интерфейс должен позволять плагинам регистрировать свои различные параметры, и пока плагин загружен, основная программа должна отображать соответствующие элементы управления GUI, представляющие каждый параметр. Например, параметром может быть int от 0 до 20 a, представленный QLabel и QSlider в блоке, или значение цвета, представленное QColorDialog.
Вот подвох: я попробовал стандартный подход ООП (?), Чтобы каждый тип параметра наследовал абстрактный класс и создавал представление GUI путем реализации виртуальной функции. Это привело к тому, что многие заголовки Qt GUI были связаны с каждым файлом плагина, увеличив его размер с ~ 20 КБ до ~ 50 КБ.
Дело не в том, чтобы сэкономить эти килобайты, а в том, чтобы лучше понять ООП. Я подумала об этом и попыталась найти подходящие шаблоны проектирования, затем я гуглила «разъединенный полиморфизм», «внешний полиморфизм» и т. Д. И наткнулась на страницу, где говорилось, что это возможно, но, как правило, вы не хотите идти туда, потому что это ломает ООП.
Так это все? Либо я скрываю код GUI от интерфейса плагина и идентифицирую каждый тип с помощью enum или чего-то еще, и «нарушаю ООП», либо класс полностью отвечает за себя, но также полностью связан внутренне?
Какие решения вы бы порекомендовали, если бы каждый тип параметра состоял из модели данных, персистентности и элементов управления GUI с сигналами? Что идет куда?
РЕДАКТИРОВАТЬ: Другими словами, мне интересно, могут ли плагины быть чистыми данными и алгоритмами, не зная, как элементы управления данными создаются в Qt и не зависят от заголовков графического интерфейса Qt. Он может использовать Q_OBJECT для сигналов, хотя.
Я бы предложил позволить плагину беспокоиться о типы его аргументов, и имеют отдельный компонент, который знает, как отобразить каждый тип на элемент управления GUI.
Это почти прямая декомпозиция модели / представления, поэтому она кажется понятной идиомой.
Теперь ваш тип модель может быть перечислена, или вы можете использовать, возможно, больше шаблонов OO Visitor, но вы по-прежнему заранее придумываете фиксированную и не очень расширяемую систему типов. Это адекватно?
Вы, вероятно, получите какой-то тип, который знает как конкретный производный тип заданного аргумента, так и детали того, как его отобразить в Qt. Это будет обрабатывать сигналы Qt и передавать значения обратно в аргумент.
… Я думаю, что через попытку dynamic_cast или чтения какого-то идентификационного кода, например, enum. Я до сих пор не понимаю, как можно использовать 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;
};
Наконец, мы можем набросать представление, которое знает, как запрашивать абстрактный плагин для его аргументов, как отображать каждый конкретный тип аргумента и как обрабатывать входные данные:
// 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
сигнал из своего виджета Spinbox и вызов model_.set()
чтобы вернуть это к плагину.
Вы можете отправить сообщение любого типа в любое место и перехватить его на другой стороне с помощью чего угодно. Виртуальные пакеты которые были сделаны именно для слабой связи с чем-либо.
Если я вас правильно понял, вы должны переосмыслить поведение. Вместо того, чтобы модуль регистрировал все (что может быть очень много) в основном приложении, вы могли бы создать базовый класс для модуля визуализации, специфичного для модуля, и фабрику в каждом модуле, которая создает конкретный модуль визуализации для модуля. Затем вы можете попросить модуль предоставить информацию, которую вы предоставляете модулю.