Russian Qt Forum

Программирование => С/C++ => Тема начата: Igors от Декабрь 24, 2014, 09:24



Название: Большой switch
Отправлено: Igors от Декабрь 24, 2014, 09:24
Добрый день

Есть баааальшой switch

Код
C++ (Qt)
void CopyParam( MyClass & dst, const MyClass & src, int id )
{
switch (id) {
  case ID_ENABLED:
    if (dst.mEnabled != src.mEnabled) {
      SaveUndo(dst);
      dst.mEnabled = src.mEnabled;
      UpdateUI();
    }
    break;
 
  case ID_COUNT:
    if (dst.mCount != src.mCount) {
      SaveUndo(dst);
      dst.mCount = src.mCount;
      UpdateUI();
    }
    break;
   ...
// и так еще много-много раз
}
Есть ли что-то лучшее?

Спасибо


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 09:26
Кот везде одинаковый, за исключением проверяемого поля? Я правильно понял?


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 09:32
С первого приближения, можно часть кода вынести в шаблон.
Код
C++ (Qt)
template<class T, class M>
void work (const T &dest, M &destMember, const M &sourceMember)
{
 if (destMember != sourceMember) {
   SaveUndo(dest);
   destMember = sourceMember;
   UpbateUI();
 }
}
 
switch(id) {
 case ID_ENABLED:
   work(dst, dst.mEnabled, src.mEnabled);
   break;
.....
}
 


Название: Re: Большой switch
Отправлено: Igors от Декабрь 24, 2014, 10:00
С первого приближения, можно часть кода вынести в шаблон.
Именно так я и сделал :)  Но

1) От switch это все-таки не избавляет (хотя значительно сокращает)

2) Код "не везде одинаковый". Да, в половине случаев из 100 - все одинаково как выше. В других напр UpdateUI не нужно. В других (еще более редких) нужно еще доп действие(я).

Можно "продолжать в том же духе", напр
Код
C++ (Qt)
void work (const T &dest, M &destMember, const M &sourceMember, bool updateUI = false, bool action1 = false........)
Но это не очень удобно вызывать
Код
C++ (Qt)
worj(dst, dst.mCount, src.mCount, false, true, false);
Как часто бывает, bool нехороший аргумент для вызывающего. Придется рисовать enum'ы. И как-то это все "распухает", дорисовать нужное в switch выглядит проще

Да, и радость с темплейт здесь возможна т.к. все члены public (как и есть в этом старом коде). Но ведь "грамотно" все через геттеры/сеттеры
Код
C++ (Qt)
int MyClass::count( void ) const { return count; }
void MyClass::setCount( int cnt ) { count = cnt; }
 
И как тогда?  :)


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 10:09
Если ты обернешь мемберов в геттер/сеттер, то можно забабахать через паттерн стратеггия.
Код
C++ (Qt)
class AbstractStrategy
{
   virtual bool canDo(int id) const = 0;
   virtual void do(MyClass & dst, const MyClass & src) = 0;
}
 

Далее делаешь стратегии, заточенные на нужные геттеры/сеттеры и необходимые действия и помещаешь их в фабрику. Твой свитч превращается в запрос у фабрики нужной стратегии и выполнения у нее do. Кода будет больше, зато очень легко можно будет расширять и понятнее кот станет.


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 10:12
Даже без геттеров/сеттеров можно, но тогда больше классов получится. Если юзать геттеры/сеттеры, можно создать меньше стратегий, передавая в конструктор std::function и инициализируя их в фабрике с передачей ссылок на нужные методы.


Название: Re: Большой switch
Отправлено: sergek от Декабрь 24, 2014, 12:52
Можно switch заменить Mapом.
В MyClass определяете много-много методов с сигнатурой что-то вроде MyClass::copy_тип_проверки(const MyClass & src, int id), ассоциативный массив типа QMap<int, MyClass::*тип_функции> и один метод copyParam с такой-же сигнатурой.
В конструкторе один раз заполняете массив, например, massiv[ID_ENABLED] = &MyClass::copy_ENABLED.
Все методы, кроме copyParam - закрытые. Метод copyParam находит в массиве нужную функцию и передает ей параметры. В общем и все.
Кода меньше не станет, но все можно попрятать в private секцию, и вся реализация - в классе, а не где-то в коде.


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 13:03
sergek, а куда засунешь вызовы SaveUndo и UpdateUI?


Название: Re: Большой switch
Отправлено: sergek от Декабрь 24, 2014, 13:16
Я могу только посоветовать, куда ;) Сам туда не хочу.
Функции глобальные - какие проблемы?


Название: Re: Большой switch
Отправлено: Igors от Декабрь 24, 2014, 13:25
Кода меньше не станет, но все можно попрятать в private секцию, и вся реализация - в классе, а не где-то в коде.
Я понял, но ф-ция копирования не особо лепится внутри класса, т.к. он должен знать про SaveUndo, UpdateUI и др - а это к нему отношения не имеет.

Не то чтобы я прямо-таки несчастен со switch'ом, ничего страшного нет, пополнять/изменять легко. Просто у меня много такого старого кода, вот и прикидываю как бы (может быть) модернизировать.

Если ты обернешь мемберов в геттер/сеттер, то можно забабахать через паттерн стратеггия.
Код
C++ (Qt)
class AbstractStrategy
{
   virtual bool canDo(int id) const = 0;
   virtual void do(MyClass & dst, const MyClass & src) = 0;
}
 

Далее делаешь стратегии, заточенные на нужные геттеры/сеттеры и необходимые действия и помещаешь их в фабрику. Твой свитч превращается в запрос у фабрики нужной стратегии и выполнения у нее do. Кода будет больше, зато очень легко можно будет расширять и понятнее кот станет.
Ничего не понял  :'(. Можно расширить Ваш псевдокод? Придираться не буду :) Да, и если можно - хоть как-то осмысленное имя вместо "do", а то я совсем растерялся.

Спасибо


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 24, 2014, 14:14
Совсем псевдокодный псевдокот.
Код
C++ (Qt)
class BoolMethodStrategy : public AbstractStrategy
{
public:
  BoolMethodStrategy(std::function<bool(MyClass*)> getMethod, std::function<void(MyClass*, bool val)> setMethod);
  virtual void copy(MyClass & dst, const MyClass & src) {
     if (getMethod_(&dst) != getMethod_(&src)............
  }
}
 
....................
map[1] = new BoolMethodStrategy(&MyClass::enabled, &MyClass::setEnabled)
 


Название: Re: Большой switch
Отправлено: _Bers от Декабрь 24, 2014, 21:43
Добрый день
Есть баааальшой switch
Есть ли что-то лучшее?

Альтернатива свитчу - полиморфизм.

Известно, что простой свитч (даже если он оч длинный стал с годами) предпочтительнее, чем ооп головного мозга.

Не рекомендую рассматривать вам выше приведенные примеры для практического использования.

модель ооп должна быть очень простой, или она не имеет смысла.
так же, она не должна создавать прецедент падению производительности (использование медленных new и прочее)


Название: Re: Большой switch
Отправлено: Akon от Декабрь 25, 2014, 03:32
Код:
class MyClass
{
public:
MyClass(bool enabled, int count) : enabled(enabled), count(count) {}

bool enabled;
int count;
};

/// Field IDs
static const int ID_ENABLED = 0;
static const int ID_COUNT = 1;

/// Primary class template and its specializations to map field Id to field pointer
template <int Id> struct MemberById;
template <> struct MemberById<ID_ENABLED> { static constexpr auto Member = &MyClass::enabled; };
template <> struct MemberById<ID_COUNT> { static constexpr auto Member = &MyClass::count; };
...

template <int Id>
void CopyParam(MyClass& dst, const MyClass& src)
{
auto& dstMember = dst.*MemberById<Id>::Member;
const auto& srcMember = src.*MemberById<Id>::Member;

if (dstMember != srcMember) {
SaveUndo(dst);
dstMember = srcMember;
UpdateUI();
}
}


Usage:
MyClass src(false, 0);
MyClass dst(true, 1);

CopyParam<ID_ENABLED>(dst, src);
CopyParam<2>(dst, src);  // compile-time error: 2 is not covered

Это идея sergek со след. отличиями:
1. весь маппинг производится в компайл-тайм (нет прецедентов падению производительности);
2. маппится не операция над конкретными полями, а смещения полей. Напр. возможно такое:
auto sum = dst.*MemberById<ID_COUNT>::Member + src.*MemberById<ID_COUNT>::Member.

Использование геттеров/сеттеров принципиальности не вносит - указателю на член будет соответствовать пара указателей на функции-члены.

В Qt подобного рода задачи решаются через MetaTypes (напр.  QObject::property()/setProperty(), где ключом выступает имя св-ва).

OOP головного мозга - это ерунда по сравнению с TMP (template metaprogramming) оного. А вообще самый быстрый вариант - это С-макро... и, зачастую, кто не брезгует, тот впереди :)


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 09:53
Известно, что простой свитч (даже если он оч длинный стал с годами) предпочтительнее, чем ооп головного мозга.
Это старый код писаный др программистом где-то в начале 90-х. Иногда заметно что он переводился с паскаля  :) Первое впечатление аховое ("о боже как примитивно!!" и.т.п.), но предложить что-то лучшее совсем непросто, во всяком случае мне это пока не удалось. Ну я особо и не рвусь, просто обдумываю.

С паттерном "стратегия" что-то совсем мрачно. Код заметно усложнился для понимания, а достигнуто лишь присваивание члена. Впрочем я никогда не понимал этих "паттернов"

Код:
template <int Id> struct MemberById;
template <> struct MemberById<ID_ENABLED> { static constexpr auto Member = &MyClass::enabled; };
Идею знал (темплейт "по числу"), но оформить бы так не сумел. Спасибо
Хорошо, теперь мы можем присваивать члены класса по id. Но пробежаться циклом (вместо switch) все равно не удается, напр
Код
C++ (Qt)
const iint id[] = { ID_ENABLED, ID_COUNT, ... };
for (int i = 0; i = sizeof(id) / sizeof(int); ++i)
CopyParam <???>   // но ведь нельзя подставить i
 
Хотя даже если бы и можно - все равно недостаточно. Т.к. не все ветки switch одинаковы, (хотя повторов очень иного). А CopyParam да, значение присвоит, но эти доп действия не выполнит


Название: Re: Большой switch
Отправлено: Bepec от Декабрь 25, 2014, 10:43
switch простейший паттерн множественного ветвления. И как вы не старайтесь у вас в результате получится тот же switch только более сложный по реализации :D

PS на мой взгляд очередная провокация Igors "А давайте сделаем молоток ещё лучше - с пилкой по металлу на другом конце рукояти" :D


Название: Re: Большой switch
Отправлено: sergek от Декабрь 25, 2014, 12:28
Я понял, но ф-ция копирования не особо лепится внутри класса, т.к. он должен знать про SaveUndo, UpdateUI и др - а это к нему отношения не имеет.
Не проблема - вынесите их в отдельный класс(ы) с интерфейсными методами. Будут работать с какими-то глобальными объектами.
Свитч - всегда причина задуматься.


Название: Re: Большой switch
Отправлено: Bepec от Декабрь 25, 2014, 12:42
Я с одним коллегой спорил на протяжении примерно 3 месяцев и ниразу он не смог предложить решения элегантнее и понятнее чем простой свич. Постоянно уходило в сторону шаблонов, фабрик, десятка другого классов с контролями ошибок и прочим. В результате свич на 4 действия превращался в монстра.
Поэтому я скептически отношусь к такого рода спорам.

Свич заменить можно, но только в единичных случаях это оправданно. ИМХО


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 12:46
Я понял, но ф-ция копирования не особо лепится внутри класса, т.к. он должен знать про SaveUndo, UpdateUI и др - а это к нему отношения не имеет.
Не проблема - вынесите их в отдельный класс(ы) с интерфейсными методами. Будут работать с какими-то глобальными объектами.
Не очень представляю как это. Можно (псевдо) код? Спасибо

switch простейший паттерн множественного ветвления. ...
Я искренне наслаждался почти полным отсутствием Ваших постов в течение последней пары месяцев. Увы, все хорошее так быстро кончается   :'(


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 13:23
Как альтернативу полиморфизму, можно использовать связку boost::variant  + соответствующий визитёр visitor

Код
C++ (Qt)
struct ID_ENABLED{};
struct ID_COUNT{};
...
 
class switch_visitor : public boost::static_visitor<>
{
public:
   void operator()(ID_ENABLED) const
   {
       // бла-бла-бла
   }
 
   void operator()(ID_COUNT) const
   {
       // бла-бла-бла
   }
 
   // и т.д.
};
 
 
boost::variant<ID_ENABLED, ID_COUNT> id = ID_COUNT();
 
 
boost::apply_visitor(switch_visitor(), id);
 

В конструктор визитора можно передавать какие-либо доп. параметры и п.р.
Пример с variant нужно воспринимать просто как альтернативу, для "подумать", возможно, подсмотреть какие-то известные решения (иначе всё "мировоззрение" так и останется замкнутым на одном лишь свитче).


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 14:00
Код
C++ (Qt)
struct ID_ENABLED{};
struct ID_COUNT{};
...
 
class switch_visitor : public boost::static_visitor<>
{
public:
   void operator()(ID_ENABLED) const
   {
       // бла-бла-бла
   }
 
   void operator()(ID_COUNT) const
   {
       // бла-бла-бла
   }
 
   // и т.д.
};
 
 
boost::variant<ID_ENABLED, ID_COUNT> id = ID_COUNT();
 
 
boost::apply_visitor(switch_visitor(), id);
 
Конечно всех деталей я не понял, но что меня смущает - классов/типов уже немало, а вот откуда возьмется нужный ф-ционал - хз. Напр UpdateUI удобно иметь методом окна, а в др методе (того же окна) сидит большой switch. Все норм. Но вот мы хотим "классами" - и надо мучительно тянуть UpdateUI  (типа корову на баню) чтобы вызвать его из того класса.

Вообще снабжать каждое поле структуры каким-то template <ID..> мне кажется не таким уж хорошим. Если это ID задается в UI - то пусть UI им и занимается, зачем это тащить в структуру данных?


Название: Re: Большой switch
Отправлено: Пантер от Декабрь 25, 2014, 14:03
Цитировать
и надо мучительно тянуть UpdateUI  (типа корову на баню) чтобы вызвать его из того класса
Передавай сцыль на метод.


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 14:22
Цитировать
Вообще снабжать каждое поле структуры каким-то template <ID..> мне кажется не таким уж хорошим. Если это ID задается в UI - то пусть UI им и занимается, зачем это тащить в структуру данных?

Я бы покурил тогда ещё вариант с мапом:

Код
C++ (Qt)
class Window
{
public:
   enum id_type { ID_ENABLED, ID_COUNT};
 
   Window()
   {
       _map[ID_ENABLED] = &Window::action_enabled;
       _map[ID_COUNT] = &Window::action_count;
   }
 
   void updateUI() { std::cout << "UbdateUI" << std::endl; }
 
   void some_method(id_type id) { _map[id](this); } // <-- Это вместо длинного свитча
 
private:
   void action_enabled()
   {
       std::cout << "ID_ENABLED" << std::endl;
       updateUI();
   }
 
   void action_count()
   {
       std::cout << "ID_COUNT" << std::endl;
   }
 
   std::map<id_type, std::function<void(Window*)>> _map;
};
 
 
int main()
{
   Window wnd;
 
   wnd.some_method(Window::ID_COUNT);
   wnd.some_method(Window::ID_ENABLED);
 
   return 0;
}
 

Кстатии, здесь также никакого полиморфизма нет)


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 15:01
Код
C++ (Qt)
   _map[ID_ENABLED] = &Window::action_enabled;
   _map[ID_COUNT] = &Window::action_count;
 
   std::map<id_type, std::function<void(Window*)>> _map;
 
Ах какой Вы быстрый  (на заглушках :)) Но напр SaveUndo - совсем не метод Window, да и UpdateUI имеет с пяток вариантов с разными параметрами. И наконец не вижу где же копируется поле структуры


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 15:15
Цитировать
Но напр SaveUndo - совсем не метод Window, да и UpdateUI имеет с пяток вариантов с разными параметрами.
И в чём проблема?

Цитировать
И наконец не вижу где же копируется поле структуры
Думал это не требует разъяснения в этом иллюстрирующем примере..

Но.. если это так не очевидно:
Код
C++ (Qt)
typedef int MyClass;
 
class Window
{
public:
   enum id_type { ID_ENABLED, ID_COUNT};
 
   Window()
   {
       _map[ID_ENABLED] = &Window::action_enabled;
       _map[ID_COUNT] = &Window::action_count;
   }
 
   void updateUI() { std::cout << "UbdateUI" << std::endl; }
 
   void some_method(MyClass & dst, const MyClass & src, id_type id) { _map[id](this, dst, src); } // <-- Вариант свитча с копированием структуры
 
private:
   void action_enabled(MyClass & dst, const MyClass & src)
   {
       std::cout << "ID_ENABLED" << std::endl;
       dst = src;
       updateUI();
   }
 
   void action_count(MyClass & dst, const MyClass & src)
   {
       std::cout << "ID_COUNT" << std::endl;
       dst = src;
   }
 
   std::map<id_type, std::function<void(Window*, MyClass&, const MyClass&)>> _map;
};
 
 
Window wnd;
 
   MyClass src = 1;
   MyClass dst = 2;
 
   wnd.some_method(dst, src, Window::ID_COUNT);
   wnd.some_method(dst, src, Window::ID_ENABLED);
 

  


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 16:22
Но.. если это так не очевидно:
Код
C++ (Qt)
Вариант свитча с копированием структуры
 
private:
   void action_enabled(MyClass & dst, const MyClass & src)
   {
       std::cout << "ID_ENABLED" << std::endl;
       dst = src;
       updateUI();
   }
 
Так присваивать-то надо не всю структуру, а лишь поле (разное для разных id). Пожалуйста прочитайте мой пост #3 в начале темы где я отвечал Пантеру


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 16:28
Цитировать
Так присваивать-то надо не всю структуру, а лишь поле (разное для разных id).
Я понимаю, что не всю, а лишь определённые поля для разных id.. Это же всего лишь пример..

Что в конце-концов мешает присваивать определённые поля?   В чём проблема то?


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 16:34
Что в конце-концов мешает присваивать определённые поля?   В чём проблема то?
Ну хотелось бы обобщить/схлопнуть код. А просто для каждого поля свой метод - ну я так всегда могу сделать, но это ничего не упрощает. 


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 17:07
Цитировать
А просто для каждого поля свой метод - ну я так всегда могу сделать, но это ничего не упрощает.
Это разбивает весь этот длинный свитч, и локализует код для каждого действия, что уже плюс..

Цитировать
Ну хотелось бы обобщить/схлопнуть код.
Ну, ну
Цитировать
Код "не везде одинаковый". Да, в половине случаев из 100 - все одинаково как выше. В других напр UpdateUI не нужно. В других (еще более редких) нужно еще доп действие(я).
А завтра окажется, что нужно добавить ещё с десяток доп. действий и условий..
И тогда все эти усилия по обобщению/схлопыванию окажутся бесполезными.     


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 17:21
А завтра окажется, что нужно добавить ещё с десяток доп. действий и условий..
И тогда все эти усилия по обобщению/схлопыванию окажутся бесполезными.     
Не исключено что и так

Это разбивает весь этот длинный свитч, и локализует код для каждого действия, что уже плюс..
Это по меньшей мере спорно. Напр в свитче нередко удобно так
Код
C++ (Qt)
bool update = false;
switch (id) {
case ID_ENABLE:
 ...
 update = true;
 break;
 ....
 ....
 update = true;
}
 
if (update) {
...
}
А вот без свитча может оказаться совсем непросто вынести действия по update в метод


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 17:30
Ну и что:
Код
C++ (Qt)
bool update = false;
_map[id](this, update);
 
if (update) { ... }
 

А вообще, если возникают сложности с локализацией свитча, то самое время пересматривать архитектуру, на которую, вероятно (очень вероятно),  как всегда забили)   


Название: Re: Большой switch
Отправлено: Old от Декабрь 25, 2014, 17:34
то самое время пересматривать архитектуру
Бегите. ;)


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 17:37
Бегите. ;)
Чувствую, сейчас будут закидывать шапками  :)


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 17:56
Ну и что:
Код
C++ (Qt)
bool update = false;
_map[id](this, update);
 
if (update) { ... }
 
Тот update могут устанавливать лишь некоторые ветки - а ф-ции мапы придется иметь параметр. И еще один .. и еще.. Уже упоминал об этом в том же посте #3

А вообще, если возникают сложности с локализацией свитча, то самое время пересматривать архитектуру, на которую, вероятно (очень вероятно),  как всегда забили) 
Таких слов "пересматривать архитектуру" лучше не употреблять :)  Даются 500K строк которые коряво но работают (что там - хз). и список фич которые надо. очень надо. И баги пофиксить, чтоб не вылетало. И OpenGL присобачить - чтоб времени не было патякать об "архитектуре". Поэтому мои робкие попытки что-то улучшить - уже подвиг  :)


Название: Re: Большой switch
Отправлено: m_ax от Декабрь 25, 2014, 18:13
Цитировать
Тот update могут устанавливать лишь некоторые ветки - а ф-ции мапы придется иметь параметр. И еще один .. и еще..
А собрать необходимые параметры в одну структуру (описывающую текущее состояние) и передавать её (или просто возвращать её).. Или не любовь к паттернам (а это тоже считается паттерном) исключает и это решение?

Код
C++ (Qt)
state s = _map[id](this);
 
if (s.update) { ... }
 
// или
 
state s;
_map[id](this, s);
 
...
 

    


Название: Re: Большой switch
Отправлено: Igors от Декабрь 25, 2014, 18:59
А собрать необходимые параметры в одну структуру и передавать её (или просто возвращать её).. Или не любовь к паттернам (а это тоже считается паттерном) исключает и это решение?
"Давайте заведем структуру и забьем туда параметры на все случаи жизни. И будем эту структуру давать каждому, а он использует нужное ему". Ну как-то это не вяжется с хорошими словами "мировоззрение", "концепт" и др из Вашего лексикона  :)

Упорное доказательство типа "все равно лучше switch не придумаете!!" совсем не входит в мои планы. Возможно для обобщений мало материала - поэтому я и создал соседний пост. Может не размениваться на один частный случай (копирование параметра), а попробовать обобщить сразу неск больших свитчей?


Название: Re: Большой switch
Отправлено: sergek от Декабрь 26, 2014, 11:23
Может не размениваться на один частный случай (копирование параметра), а попробовать обобщить сразу неск больших свитчей?
Тема, на мой взгляд, полезная. Рассмотреть разные случаи из практики, варианты замены свитча с использованием объектного подхода, шаблонов етц. Хороший может быть материал.