Шаблон проектирования для дополнительных функций?

У меня есть базовый класс, от которого наследуются производные подклассы, он содержит основные функции, которые должны быть одинаковыми для всех производных классов:

class Basic {
public:
    Run() {
        int input = something->getsomething();
        switch(input)
        {
            /* Basic functionality */
            case 1:
                doA();
                break;
            case 2:
                doB();
                break;
            case 5:
                Foo();
                break;
        }
    }
};

Теперь, основываясь на производном классе, я хочу "добавить" больше операторов case в коммутатор. Какие у меня варианты здесь? Я могу объявить виртуальные функции и определить их только в производных классах, которые будут их использовать:

class Basic {
protected:
    virtual void DoSomethingElse();
public:
    Run() {
        int input = something->getsomething();
        switch(input)
        {
            /* Basic functionality */
            ...

            case 6:
                DoSomethingElse();
        }
    }
};


class Derived : public Basic {
protected:
    void DoSomethingElse() { ... }
}

Но это будет означать, что при изменении функций в любом производном классе мне придется отредактировать базовый класс, чтобы отразить эти изменения.

Есть ли шаблон дизайна специально для такого рода проблем? Я купил несколько книг по шаблонам проектирования, но я изучаю их по мере необходимости, поэтому я понятия не имею, есть ли такой шаблон, который я ищу.

6 ответов

Решение

Возможно, вам будет полезно прочитать о модели цепочки ответственности и переосмыслить свое решение таким образом.

Также вы можете объявить doRun как защищенный метод и вызвать его в базовом случае по умолчанию.

default:
   doRun(input);

И определить doRun в производных классах.

Это так называемый шаблонный шаблон

Я думаю, что вам нужен паттерн Chain Of Responsibility или, возможно, Strategy в сочетании с динамической таблицей вызовов...

Простое решение:

class Basic {
  public:
    void Run() {
      const int input = ...
      if (!(BaseProcess(input) || Process(input))) ...
    }

    vitual bool Process(int input) { return false; }

    bool BaseProcess(int input) {
      switch(input) {
    ...
        default: return false;
      }
      return true;
    }
...

... а затем реализовать дополнительные случаи в подклассе Process(). Если вам нужно поддерживать более 2 уровней (т.е. подкласс добавляет еще больше случаев), то вам понадобится таблица динамической отправки.

Обычный способ справиться с этим - использовать фабрику. В общих чертах:

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

Теперь за добавленные бонусные баллы:

  • создайте схему, которая повторно регистрирует классы на фабрике - вам нужно будет указать входные данные и тип класса, чтобы справиться с ним

Теперь, когда возникает необходимость в новом входе, вы просто выводите новый класс и регистрируете его на фабрике. Потребность в выражении switch исчезает.

Но это будет означать, что при изменении функций в любом производном классе мне придется отредактировать базовый класс, чтобы отразить эти изменения.

Почему это так?

Я освещаю ваш комментарий - тогда, если вы выберете такой подход, у вас возникнут эти проблемы. Другие опубликовали ответы, которые предлагают другие решения - я бы проверил их, чтобы посмотреть, помогут ли они вам.

Если ваши значения селектора являются просто маленькими целыми числами, я бы заменил оператор case таблицей поиска. (Каждое действие в этом случае необходимо кодировать как функцию, поэтому вы можете поместить указатели на функции в таблицу). Тогда унаследованные классы могут просто добавлять записи в таблицу. (Я думаю, таблица должна быть свойством экземпляра, оно не может быть статическим).

Colin

Другие вопросы по тегам