Какой дизайн паттерн использовать?
От: k55 Ниоткуда  
Дата: 02.07.21 21:28
Оценка:
День добрый.
Есть обертка над DBUS которая получает event. Каждый евент может содержать набор параметров, т.е. у каждого события свой набор параметров.

Хочется сделать так, что бы внешний класс мог подписаться на событие и получить этот набор данных вместе с событием.
так же хочется иметь единый способ "подписки" без введения разных API зависищих от события и типов данных.

Простой вариант это иметь массив "variant"-ов (а-ля с труктура union с типов данных в ней) и передавать его в Callback.

Вот примерный код.
struct TCallBack
{
  void Callback(const string &name, vector<variant> params);
}

struct TWrapper
{
 void Subscribe(const string &name, CallBack *callback);
 TCallBack *m_callback;
 void loop()
 {
     if (event)
     {
        vector<variant> params = GetParamsFromEvent(event);
        m_callback->Callback(event->name, params);
     }
 }
}


Или делать "..." в сигнатуре калбэка а подписчик сам ищет что ему надо.

Какой еще есть способ что бы уйти от "вариантов" и спрятать работу с event в обертке.
Ограничения: C++11.
Если есть желание — найдется 1000 возможностей.
Если нет желания — найдется 1000 причин.
Re: Какой дизайн паттерн использовать?
От: σ  
Дата: 02.07.21 21:45
Оценка: 6 (1) +1 -1
Предлогаю дизайн-паттерн "Интерпретатор".
Из цепепе-кода дёргается питон-скрипт, который занимается всей динамикой:
#include <cstdlib>

int main()
{
    return system("python ...");
}
Re: Какой дизайн паттерн использовать?
От: Evgeny.Panasyuk Россия  
Дата: 02.07.21 23:27
Оценка:
Здравствуйте, k55, Вы писали:

k55>Какой еще есть способ что бы уйти от "вариантов" и спрятать работу с event в обертке.


У каждого подписчика одна сигнатура? Или набор сигнатур с перегрузкой по ним?
А набор событий ограничен? У каждого события только одна сигнатура? ...

Уходить от вариантов надо в том месте, где соответствующая информация ещё не стёрта.
Например в месте подписки тебе скорей всего известна сигнатура подписчика, её можно либо интроспектировать, либо же потребовать описать явно шаблонным параметром. А может с каждым событием жёстко связанна одна сигнатура, вот это и нужно описать в системе типов.
И далее, зная сигнатуру в месте подписки, можно создать wrapper который будет уметь проверять типы пришедших параметров, после этого конвертировать их в конкретные типы и передавать подписчику. wrapper может быть в виде лямбды, которая потом сохраняется в std::function.
Re: Какой дизайн паттерн использовать?
От: Vamp Россия  
Дата: 03.07.21 02:22
Оценка: 1 (1)
Здравствуйте, k55, Вы писали:

k55>Какой еще есть способ что бы уйти от "вариантов" и спрятать работу с event в обертке.

k55>Ограничения: C++11.
Типизировать callback типом эвента. Заодно упростится dispatch. Ну как-то так:

struct EventA {
   static constexpr auto id = 0;
   
   template <class T>
   static void process(const char* raw, T&& callback) {
      // get data from char array
      callback(data1, data2, data3);
   }
};

std::map<int, std::vector<std::function<void (const char*)>> subs;
template<class Event, class Subscriber>
void subscribe(Subscriber sub)
{
    subs[Event::id].push_back([sub](const char* raw) { Event::process(raw, sub); });
}
Да здравствует мыло душистое и веревка пушистая.
Отредактировано 03.07.2021 2:24 Vamp . Предыдущая версия .
Re: Какой дизайн паттерн использовать?
От: kov_serg Россия  
Дата: 03.07.21 08:18
Оценка:
Здравствуйте, k55, Вы писали:

k55>День добрый.

k55>Есть обертка над DBUS которая получает event. Каждый евент может содержать набор параметров, т.е. у каждого события свой набор параметров.

k55>Хочется сделать так, что бы внешний класс мог подписаться на событие и получить этот набор данных вместе с событием.

k55>так же хочется иметь единый способ "подписки" без введения разных API зависищих от события и типов данных.

k55>Простой вариант это иметь массив "variant"-ов (а-ля с труктура union с типов данных в ней) и передавать его в Callback.

k55>Или делать "..." в сигнатуре калбэка а подписчик сам ищет что ему надо.
k55>Какой еще есть способ что бы уйти от "вариантов" и спрятать работу с event в обертке.
k55>Ограничения: C++11.

Про питон уже писали. Но можно использовать контекст.
Из готовых например lua
typedef void event_handler_fn(void* ctx, lua_State *L);

И у контекста спрашивать, что за событие и параметры и так же их вынимать.
Более того получите возможность расширять функционал скриптами без перекомпиляции.
Re: Какой дизайн паттерн использовать?
От: TailWind  
Дата: 03.07.21 09:56
Оценка: -1
k55>Хочется сделать так, что бы внешний класс мог подписаться на событие и получить этот набор данных вместе с событием.

Сохранить указатель на класс

И в момент события вызывать его функции

Используйте шаблоны только в крайнем случае
Если без них никак
Re: Какой дизайн паттерн использовать?
От: Mr.Delphist  
Дата: 05.07.21 07:56
Оценка:
Здравствуйте, k55, Вы писали:

k55>День добрый.

k55>Есть обертка над DBUS которая получает event. Каждый евент может содержать набор параметров, т.е. у каждого события свой набор параметров.

k55>Хочется сделать так, что бы внешний класс мог подписаться на событие и получить этот набор данных вместе с событием.

k55>так же хочется иметь единый способ "подписки" без введения разных API зависищих от события и типов данных.

Параметр как набор из переменного числа key-value pair? А далее уже задача подписчика разобрать этот контейнер и начать пользоваться.
Re: Какой дизайн паттерн использовать?
От: B0FEE664  
Дата: 06.07.21 15:39
Оценка:
Здравствуйте, k55, Вы писали:

k55>Есть обертка над DBUS которая получает event. Каждый евент может содержать набор параметров, т.е. у каждого события свой набор параметров.

k55>Хочется сделать так, что бы внешний класс мог подписаться на событие и получить этот набор данных вместе с событием.
k55>так же хочется иметь единый способ "подписки" без введения разных API зависищих от события и типов данных.
k55>Простой вариант это иметь массив "variant"-ов (а-ля с труктура union с типов данных в ней) и передавать его в Callback.

Не, этот вариант сложный. Простой вариант — это передавать ссылку на обёртку в callback:

class TWrapper;

struct TCallBack
{
  void Callback(const string& name, const TWrapper& rDataProvider);
}

class TWrapper
{
 void Subscribe(const string &name, TCallBack* callback);
 TCallBack *m_callback;
 void loop()
 {
     if (event)
     {
        m_callback->Callback(event->name, this);
     }
 }
}


void TCallBack::Callback(const string& name, const TWrapper& rDataProvider)
{
   std::uint32_t param1 = rDataProvider.GetParamAsUInt32<1>(); // 1 - номер параметра
   // или
   auto param1 = rDataProvider.GetParamAsUInt(1);
   auto param2 = rDataProvider.GetParamAsString(2);
   ...
   // 
   // вообще тут можно определить любой интерфейс - это дело вкуса и усердия:
   std::uint32_t param1;
   std::string  param2;
   rDataProvider.GetParams(&param1, &param2);
}



Если же делать не по простому, то TWrapper надо обернуть в класс (или отнаследоваться от интерфейса), который интерфейсом выдаёт функции получения параметров.
Сложности возникнут только если параметры — это структуры и массивы. Для них, по моему опыту, удобно делать внешние (глобальные) функции конвертирования. Что-то вроде:

struct Asdf
{
   std::string str;
   int         value; 
};

Asdf ConvertToAsdf(const TWrapper& rDataProvider)
{
   // парсинг rDataProvider ---> Asdf 
}
И каждый день — без права на ошибку...
Re: Какой дизайн паттерн использовать?
От: Умака Кумакаки Ниоткуда  
Дата: 15.07.21 12:13
Оценка:
Здравствуйте, k55, Вы писали:


k55>Простой вариант это иметь массив "variant"-ов (а-ля с труктура union с типов данных в ней) и передавать его в Callback.

для начала и так сойдёт, но в перспективе всё-таки захочется более сильного контроля над типами, поэтому я бы сделал так:
struct event_handler{
  virtual on_mouse_event(int x, int y) {};
  virtual on_file_system_event(file_desc f, e_file_event_data data) {}
  virtual on_network_event(socket s, socket_data d) {};
};

void loop() {
  if (event) 
    dispatch_dbus_event(event);
  }
}

void dispatch_dbus_event(event) {
  using std::for_each;
  switch (event.type) {
    case e_dbus_mouse: {
      const auto info = dbus_event_to_mouse_info(event);
      for_each(handlers.begin(), handlers.end(), [](event_handler& handler) { handler.on_mouse_event(info.x, info.y) });
      break;
    } 
    case e_dbus_file_system: {
      const auto info = dbus_event_to_filesystem_info(event);
      for_each(handlers.begin(), handlers.end(), [](event_handler& handler) { handler.on_file_system_event(info.f, info.data) });
      break;
    } 
    case e_dbus_network: {
      const auto info = dbus_event_to_network_info(event);
      for_each(handlers.begin(), handlers.end(), [](event_handler& handler) { handler.on_network_event(info.socket, info.data) });
      break;
    } 
  }
}


Вместо виртуальных функций можно использовать std::function, будет чуть меньше связность, но меньше и сцепленность
нормально делай — нормально будет
Отредактировано 15.07.2021 12:14 Умака Кумакаки . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.