Я пытаюсь найти элегантный способ реализации алгоритма принятия решений, который позволяет легко обслуживать, потому что условия для принятия решений могут часто меняться.
Я собираюсь попытаться быть более конкретным с примером здесь:
Допустим, я пытаюсь управлять командой поваров-кулинаров на кухне ресторана.
Каждый шеф-повар знает, как готовить 3 вида пирогов: яблочный, тыквенный и малиновый, а также 2 вида пиццы: сырная пицца и беконная пицца. Они все умеют все готовить.
Теперь я хотел бы отправить распоряжения этим начальникам о том, что будет с клиентами.
Условия:
Шеф может готовить только один пирог за раз. Например, если я приказываю шеф-повару приготовить яблочный пирог, я не могу приказать ему приготовить малиновый пирог или тыквенный пирог, если яблочный пирог не готов или я не отправил запрос на отмену яблочного пирога.
Я могу попросить шеф-повара приготовить до 5 пицц за раз, учитывая, что это для разных клиентов.
Мне бы хотелось создать алгоритм, который возвращает набор заказов, которые мне разрешено отправить конкретному шеф-повару, относительно того, что он уже готовит.
Я работаю с C ++.
Я мог бы написать простую инструкцию switch / case, но обслуживание было бы нелегким, если бы изменились условия или были добавлены новые пироги, и так …
Я немного застрял и действительно не понимаю, как я мог бы заключить в капсулу условия и принятие решений, чтобы уменьшить сопряженные условия и облегчить обслуживание в условиях приготовления пирогов.
Как бы вы справились со сложной реализацией алгоритма принятия решений?
Я мог бы написать простую инструкцию switch / case, но обслуживание было бы нелегким, если бы изменились условия или были добавлены новые пироги, и так …
Я немного застрял и действительно не понимаю, как я мог бы заключить в капсулу условия и принятие решений, чтобы уменьшить сопряженные условия и облегчить обслуживание в условиях приготовления пирогов.
Как бы вы справились со сложной реализацией алгоритма принятия решений?
Классическое изменение / рефакторинг от переключателя / случая к поддерживаемому ООП состоит в замене каждого условия и действия на специализацию / реализацию абстрактного класса.
Старый код:
variable_t variable; // initialized elsewhere
switch(variable) {
case value1:
do_action1();
break;
case value2:
do_action2();
break;
// ...
}
Новый код:
struct Actor // actor is your "abstract chef"{
virtual ~Actor();
virtual bool matches(variable_t const v) const = 0;
virtual void do_action() = 0;
};
Теперь для каждой комбинации действия и условия вы создаете одну специализацию:
struct SweedishChef: public Actor {
bool matches(variable_t const v) const override
{
return v == 1;
}
void do_action() override
{
std::cerr << "bork! bork!\n";
}
};
При этом в клиентском коде больше нет ничего закодированного.
Инициализация клиентского кода:
std::vector<std::unique_ptr<Actor>> actors;
actors.emplace_back( new SweedishChef{} };
// adding a new type of chef simply means adding _one_ line of
// code here, for the new type
Код принятия решений (замена старого switch
код):
// using std::find, begin, end
variable_t variable; // initialized elsewhere
const auto chef = find(begin(actors), end(actors),
[&v](const std::unique_ptr<Actor>& a) { return a->matches(v); });
if(chef != end(actors))
chef->do_action();
else
{
// here goes whatever was in the default part of the switch code
}
С точки зрения обслуживания и тестируемости, этот код намного проще в обслуживании:
Изменения в коде клиента минимальны при добавлении нового шеф-повара
взаимодействие между шеф-поварами и командами было оформлено (и заморожено) за интерфейсом
каждое условие / действие может (и должно быть) проверено отдельно
Диспетчерский механизм может быть проверен отдельно (и с поддельными игроками, для попадания в различные случаи).
механизм инициализации можно протестировать отдельно
Нет, это слишком много, чтобы закодировать. Просто посчитайте символы. Этот вид кодирования не гарантирует освобождение свободной памяти для «нового SweedishChef {}». И размер клиента стал длиннее в объявлении переменной.