ncix
@ncix
Предприниматель

Матричный полиморфизм?

Коллеги, добрый день.

Возникла архитектурная задачка, которую сходу не осилил.
Итак, в приложении есть некий процессинг данных, зависящий от двух параметров(m, n). В зависимости от комбинации параметров могут выполнятся какие-то действия, иногда одинаковые, для разные параметров, иногда разные. Итого имеем матрицу MxN, где в каждой ячейке которой — некое действие A. Соотношение количества вариантов параметров и действий примерно следующее M~=N~=A/3.

Старая реализация сделана в виде гигантского switch по M c вложенными свитчами по N (всего 230 строк на Delphi).

Типичный кусок выглядит примерно так (в псевдокоде)
case m_5:
{
  switch(n)
  case (n3,n5,n6,n18): {какой-то небольшой код}
  case (n4,n6,n8,n9): {какой-то небольшой код}
  default: {какой-то небольшой код}
}


Сейчас этот код переносится на С++ и захотелось сделать его красивым набором понятных классов. Сначала хотел сделать классы для каждого N с необходимыми виртуальными методами для М. Но тогда получается много дублирования кода, т.к. для заданного N действия для нескольких M обязательно совпадают. То же самое если поменять M на N. Если сделать набор классов для А, параметризовав фабрику значениями (m,n), то во-первых получается дофига классов А, а во-вторых сильно теряется читаемость.

Сейчас склоняюсь к тому чтобы повторить всё как есть, но буду рад если подскажете прорывную идею.
  • Вопрос задан
  • 4084 просмотра
Пригласить эксперта
Ответы на вопрос 8
Ramires
@Ramires
Вроде в книге Макконнелла «Совершенный код» была глава про табличные методы. Прочтите её(страниц 20-30 всего), может она Вам поможет. Самый простой вариант, который придумал сходу:
создайте таблицу, в каждой ячейке которой будет callback на нужную ф-ию.
Возможно, я ошибаюсь, и Вам обязательно нужны классы.
Ответ написан
TheHorse
@TheHorse
Реализовываем все функции Ai:
function() A1() {};
function() A2() {};
function() A3() {};


Далее берем указатели на все эти функции и записываем их в Pi;
Далее берем матрицу MхN и записываем в соответствующие i,j индекс нужной функции.

После чего, по i,j берем указатель на нужную функцию и выполняем ее.
Как-то так.

Можно реализовать без указателей, сделав одну функцию A(i) которая по нужному i выполняет нужную функцию.

P. S. Быть может, я свято верю, функции похожи, и отличаются лишь параметрами, что может улучшить реализацию.
Ответ написан
@motl
Если M — множество классов и N — множество классов, то это классический visitor pattern.
Ответ написан
@rtorsten
Я бы наверное сделал, что-то типа такого:

typedef void (*HandlerFunction)(<params>); // or typedef std::function<void (params)> HandlerFunction;

struct HandlerKey { int m; int n; } // реализуем так же операторы для сравнения, конструктор ...
typedef std::map<HandlerKey, HandlerFunction> HandlerMap;

void init(HandlerMap & m)
{
    m[HandlerKey(m1, n2)] = HandlerFunction1;
    m[HandlerKey(m1, n2)] = HandlerFunction2;
    m[HandlerKey(m2, n1)] = HandlerFunction1;
    m[HandlerKey(m2, n2)] = HandlerFunction3;
    ...
}

void process(const HandlerKey & key, <params>)
{
    HandlerMap::iterator it = handlerMap.find(key);
    if (it == handlerMap.end()) 
         std::runtime_error("No handler for key " + key.toString());
    it->second(<params>);
}


Я сделал callback стайл, но если активно используется ООП, то наверное лучше что-то типа функтора сделать, т.к. скорее всего хендлеры используют схожий функционал, который можно расшарить через базовые классы.
Ответ написан
@EaS
Присваивание можно сделать функцией с переменным числом аргументов, я думаю:
init(ValueType value, int line, int column, ...); // прототип
init(value, line, column1, column2, column3); // так будет выглядеть вызов
тогда не будет m*n присваиваний.

Если используете свежую версию gcc, то подобное же можно будет сделать через variadic templates (правда не знаю, зачем :) )
Ответ написан
Комментировать
bfDeveloper
@bfDeveloper
У вас описано одно из лучших решений: M классов по N методов. Дублирования легко избежать, реализовав всё по 1 разу, а в остальных методах просто вызывать уже реализованные. Ваша задача для ООП стиля гуглится по фразе «множественный полиморфизм». Кажется, у Меерса предлагалось решение в виде map<key, function>, в которую просто записываются указатели на функции так, как вам уже предлагали выше.
Ответ написан
Комментировать
ncix
@ncix Автор вопроса
Предприниматель
Дублирования легко избежать, реализовав всё по 1 разу, а в остальных методах просто вызывать уже реализованные

Специфика задачи такова, что реализация каждого действия А и так не более 1 строчки, поэтому перевызовы создадут кучу оберток над очень скромным содержанием. Именно поэтому такой вариант не очень и устраивает.
Ответ написан
Комментировать
@NTSky
Спасибо за Ваш вопрос. Он заставил меня задуматься над Вашей задачей и в итоге я написал мою первую статью на Хабр, за которую получил инвайт. habrahabr.ru/post/183332/ надеюсь она будет Вам полезна.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы