среда, 8 октября 2014 г.

[prog.c++] Нужен совет по украшательству кода в SObjectizer

Фактически, по следам вчерашней темы. Понравилось мне сокращать многословность :) Теперь боюсь, что не смогу вовремя остановится ;)

Вот, например. Один из краеугольных камней в SObjectizer -- это наличие явно выраженных состояний агентов. Причем не только в SObjectizer, в libcaf/libcppa состояния тоже есть, но называются они там behavior. Что не удивительно, т.к. и в SO, и в CAF, агенты представляют из себя конечные автоматы, а какие же КА без состояний? :)

Так вот, до вчерашнего дня смена состояния агента выполнялась методом so_change_state()

virtual void
so_define_agent() override
{
  so_change_state( st_free );

  so_subscribe( so_direct_mbox() ).in( st_free )
    .event( [this]( const msg_take & evt )
      {
        so_change_state( st_taken );
        evt.m_who->deliver_signal< msg_taken >();
      } );

Очень долгое время такой вариант казался нормальным, хотя иногда проскакивала мысль, что state.activate() выглядит получше. В конце-концов попробовал, вроде действительно получше:

virtual void
so_define_agent() override
{
  st_free.activate();

  st_free.handle( [this]( const msg_take & evt )
      {
        st_taken.activate();
        so_5::send< msg_taken >( evt.m_who );
      } );

Но ведь и это не предел. А что, если задействовать переопределение операторов. Например:

virtual void
so_define_agent() override
{
  this >> st_free;

  st_free.handle( [this]( const msg_take & evt )
      {
        this >> st_taken;
        so_5::send< msg_taken >( evt.m_who );
      } );

У меня, конечно, извращенные представления о прекрасном, но мне кажется, что запись this >> st_free более явно говорит о переходе агента в состояние st_free, чем st_free.activate().

Соответственно, интересует мнение читателей: есть ли смысл делать this >> st_free вместо st_free.activate()? Или это, напротив, будет шагом к превращению программы в криптограмму?

Следующий момент связан с тем, что давно привычная цепочка подписки посредством so_subscribe().in().event() позволяла определить один обработчик события для нескольких состояний сразу (что удобно, если реакция на сообщение/сигнал в этих состояниях одинаковая):

so_subscribe( mbox )
  .in( st_waiting_connection_result )
  .in( st_waiting_auth_result )
  .in( st_waiting_reconnection )
  .in( st_closing_session )
  .event(
      so_5::signal< msg_shutdown >,
      &connection_handler::evt_shutdown );

Теперь для подписки можно использовать метод state_t::handle, но он привязывает обработчик только к одному состоянию. И данный с использованием этого метода будет выглядеть так:

st_waiting_connection_result.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_waiting_auth_result.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_waiting_reconnection.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );
st_closing_session.handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );

Что, на мой взгляд, гораздо хуже. Посему напрашивается вариант с переопределением operator+()

(st_waiting_connection_result + st_waiting_auth_result + st_waiting_reconnection + st_closing_session)
  .handle< msg_shutdown >( mbox, &connection_handler::evt_shutdown );

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

Тут мне нужен второй совет: есть ли смысл определять подобным образом operator+() для привязывания обработчика сразу к нескольким состояниям? Или это еще один способ обсфукации кода?

Важное дополнение: старые методы и приемы никуда не уходят, они остаются. Причем не только для сохранения совместимости (ради этого они бы остались в любом случае), но и потому, что именно они сидят под капотом в новых, более компактных вариантах подписки. Поэтому пользователь сможет использовать любой удобный ему подход.

PS. Совсем уже "оторванный" вариант фантазия подсказала:

virtual void
so_define_agent() override
{
  st_normal.handle( m_notification_mbox,
      [this]( const msg_config_changed & evt ) {
        accept_new_config( evt );
      } );

  st_normal.handle< msg_shutdown >( m_notification_mbox,
      [this]() {
        initiate_shutdown();
      } );
}
virtual void
so_define_agent() override
{
  st_normal & m_notification_mbox +=
      [this]( const msg_config_changed & evt ) {
        accept_new_config( evt );
      };

  st_normal & m_notification_mbox & signal< msg_shutdown > +=
      [this]() { initiate_shutdown(); };
}

Комментариев нет: