Общие (почти) самоописывающие параметры в С++ в сочетании с графическим интерфейсом?

Я столкнулся со следующей загадкой: наше программное обеспечение имеет абстрактный базовый класс для объектов алгоритма. Все эти объекты имеют общий метод execute(), например:

class Algorithm
{
public:
  // [...]
  virtual void execute() = 0;
  // [...]
};

Для каждого алгоритма, который мы хотим реализовать, мы просто наследуем базовый класс и сохраняем все загруженные объекты алгоритма в центральном расположении. Пока что так хорошо.

Теперь проблема заключается в параметрах алгоритма. Мы хотим уметь описывать для каждого алгоритма параметры, которые необходимо установить (внешним классом). С этой целью мы дали каждому алгоритму объект ParameterList, который содержит его параметры. Я должен уточнить, что для нас параметр состоит из какого-то типа (например, int) и метки (например, "Число итераций" ).

Теперь проблема начинается, когда мы хотим подключить ParameterList к некоторому графическому интерфейсу. Очевидно, что наши алгоритмы не должны иметь "знания" графического API (Qt, GTK и т.д.), Которые мы используем. Тем не менее, с одной стороны, мы хотим иметь возможность описать параметры алгоритма семантически, например, указав, что для алгоритма требуется имя файла. Как показано это имя файла, до GUI.

Есть ли способ объединить этот ParameterList с каким-то знанием семантического типа?

Я понимаю, что этот вопрос звучит очень расплывчато. Однако мне не разрешено публиковать какие-либо нетривиальные примеры кода (по причинам NDA). Итак, кто-нибудь сталкивался с аналогичной проблемой в прошлом?

Чтобы обернуть это: мы хотим, чтобы наши объекты описывали параметры, которые они требуют графическому интерфейсу, не зная точную информацию о графическом интерфейсе.

Ответ 1

Одним из вариантов может быть использование шаблона посетителя. Вы можете создать базовый класс следующим образом:

class Parameter {
public:
    virtual ~Parameter() {} // Polymorphic classes need virtual dtors.

    virtual void accept(ParameterVisitor& v) = 0;
};

Вы можете определить такие подклассы, как эти:

class IntParameter: public Parameter {
public:
     virtual void accept(ParameterVisitor& v) {
          v.visit(*this);
     }
};
class FilenameParameter: public Parameter {
public:
     virtual void accept(ParameterVisitor& v) {
          v.visit(*this);
     }
};

Обратите внимание, что в каждой функции-члена accept тип *this является статическим типом класса, а именно, IntParameter& в первом случае, а FilenameParameter& во втором случае.

Затем вы можете определить класс базового класса ParameterVisitor следующим образом:

class ParameterVisitor {
public:
    virtual ~ParameterVisitor() {} // Polymorphic classes need virtual dtors.

    virtual void visit(IntParameter& p) {}
    virtual void visit(FilenameParameter& p) {}
    /* .. etc. .. */
};

Затем вы можете подклассифицировать этого посетителя, чтобы получить информацию о типе:

class Gui1ParameterVisitor: public ParameterVisitor {
public:
    virtual void visit(IntParameter& p) {
        /* ... use GUI1 to create a field for an integer. */
    }
    virtual void visit(FilenameParameter& p) {
        /* ... use GUI1 to create a field for a filename. */
    }
};

class Gui2ParameterVisitor: public ParameterVisitor {
public:
    virtual void visit(IntParameter& p) {
        /* ... use GUI2 to create a field for an integer. */
    }
    virtual void visit(FilenameParameter& p) {
        /* ... use GUI2 to create a field for a filename. */
    }
};

Затем ваш класс ParameterList может просто сохранить список Parameter* s. Затем вы можете создать графический интерфейс, создав соответствующий тип посетителя, а затем получив обратные вызовы visit, выполнив всю конструкцию виджета. Это заканчивается безопасностью типа и восстанавливает необходимую вам информацию. У этого есть недостаток: каждый раз, когда вы создаете новый тип параметра, вам нужно добавить новую функцию-член visit в класс ParameterVisitor, но вам все равно нужно сделать все это для создания графического интерфейса.

Надеюсь, это поможет!