Шаблон проектирования для дополнительных функций?
У меня есть базовый класс, от которого наследуются производные подклассы, он содержит основные функции, которые должны быть одинаковыми для всех производных классов:
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