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

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

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() { ... }
}

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

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


person Daniel Sloof    schedule 21.05.2009    source источник


Ответы (6)


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

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

default:
   doRun(input);

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

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

person Mykola Golubyev    schedule 21.05.2009
comment
Я думаю, что сейчас я выберу шаблон Template Method. Я обязательно прочитаю и рассмотрю другие шаблоны, перечисленные здесь, но пока этот можно пометить как принятый :) - person Daniel Sloof; 21.05.2009

Я думаю, что вам нужен шаблон Цепочка ответственности или, может быть, Стратегия в сочетании с динамической таблицей вызовов...

person fortran    schedule 21.05.2009

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

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

Теперь о дополнительных бонусных баллах:

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

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

person Community    schedule 21.05.2009
comment
Я попытаюсь еще немного пояснить свои рассуждения: мне не нужно производить производные на основе ввода: производные классы не будут жить в стеке. Я просто хочу разделить разные типы логики, но при этом сохранить некоторую «базовую» логику для всех производных классов. - person Daniel Sloof; 21.05.2009

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

Колин

person Colin Fine    schedule 21.05.2009
comment
Я думал об использовании указателей на функции, но я относительно новичок в С++ и еще не изучил их достаточно глубоко, чтобы знать, как их использовать с различным использованием параметров. - person Daniel Sloof; 21.05.2009

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

Почему это может быть правдой?

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

person ChrisF    schedule 21.05.2009
comment
Ну наверное я плохо выразился. Мне не нужно делать это, когда я изменяю содержимое функции, но когда я удаляю функцию или меняю ее имя, мне придется вернуться к классу Basic, чтобы отразить эти изменения. Кроме того, я не хочу, чтобы в итоге получился «базовый» класс с кучей виртуальных функций; по одному для каждой функции в любом классе, производном от него. - person Daniel Sloof; 21.05.2009
comment
это верно только в том случае, если базовый класс должен иметь виртуальные функции... будет ли какой-либо фрагмент кода вызывать такие функции для объектов базового класса или они предназначены только для частного использования в подклассах? если тип классов известен для каждого объекта (то есть вам не нужны полиморфы, что, кажется, так и есть), вам не нужно объявлять виртуальные функции в базовом классе. - person fortran; 21.05.2009

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

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 уровней (т. е. подкласс, добавляющий еще больше случаев), вам понадобится динамическая таблица диспетчеризации.

person Igor Krivokon    schedule 21.05.2009