Command pattern совместно с Composite pattern

syfisher

TDD infected!!
Command pattern совместно с Composite pattern

Мне нужно реализовать разбиение сложных комманд на серию более простых, которые можно было бы использовать отдельно. По сути это применение Composite к коммандам.

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

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

Что-то типа того:

PHP:
class composite_command
{
  protected _child_commands = array();

  public function perform()
 {
   if $this->_do_self_perform()
     $this->_perform_child_commands();
 }

  protected function _do_self_perform()
  {
   // свой метод исполнения.
  }

  protected function _perform_child_commands()
  {
     foreach($this->_child_commands as $command)
       $command->perform();
  }
}
Может у кого есть ссылки на примеры подобных реализаций (можно на java) или свой опыт?
 

pachanga

Новичок
Re: Command pattern совместно с Composite pattern

Не совсем понятно, что имеется в виду, хотя могу предположить, что основной целью является разбиение сложной логики на атомарные команды.

Мы столкнулись со схожей проблемой и на текущий момент нашли приемлемое и достаточно гибкое решение: некий гибрид конечного автомата.

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

Что-то типа:

PHP:
class articles_folder_controller()
{
 [...]
  public function define_action_delete($state_machine)
  {
    $state_machine->registerState('use_view', 
       array(LIMB_COMMON_DIR . '/commands/use_view_command', '/site_object/delete.html'),
       array(LIMB :: STATUS_OK => 'form')
    );

    $state_machine->registerState('form', 
       LIMB_COMMON_DIR . '/commands/delete_objects_form_command',
       array(LIMB :: STATUS_FORM_DISPLAYED => 'render_view', 
             LIMB :: STATUS_FORM_SUBMITTED => 'delete_records'),
    );

    $state_machine->registerState('delete_records', 
       LIMB_COMMON_DIR . '/commands/delete_site_objects_command',
       array(LIMB :: STATUS_OK => 'delete_search_index')
    );

    $state_machine->registerState('delete_search_index', 
       LIMB_SEARCH_DIR . '/commands/delete_search_index_command',
       array(LIMB :: STATUS_OK => 'delete_access_records')
    );

    $state_machine->registerState('delete_access_records', 
       LIMB_SIMPLE_PERMISSOIONS_DIR . '/commands/delete_access_records_command',
       array(LIMB :: STATUS_OK => 'close_popup')
    );
    
    $state_machine->registerState('render_view', 
       LIMB_COMMON_DIR . '/commands/render_view_command'
    );

    $state_machine->registerState('close_popup', 
       LIMB_COMMON_DIR . '/commands/close_popup_command'
    );
  }

}
Как это работает:

PHP:
    $state_machine->registerState('form', 
       LIMB_COMMON_DIR . '/commands/delete_objects_form_command',
       array(LIMB :: STATUS_FORM_DISPLAYED => 'render_view', 
             LIMB :: STATUS_FORM_SUBMITTED => 'delete_records'),
    );
Мы указываем, что состоянию 'form' соответствует команда LIMB_COMMON_DIR . '/commands/delete_objects_form_command', которая может возвращать статусы LIMB :: STATUS_FORM_DISPLAYED и LIMB :: STATUS_FORM_SUBMITTED, при получении который автомат переходит в состояния 'render_view' и 'delete_records' соответственно.

На данный момент такие описания составляются вручную, хотя в принципе, планируется сделать генерацию кода на основе xml(или простого ini) описания.
 

fisher

накатила суть
имхо слишком абстрактная задача, чтобы получить корректный ответ. думаю, обычные if'ы в конечном итоге в подавляющем большинстве случаев будут быстрее и понятнее мудреных решений...

вариант pacha, может, и забавный, но вряд ли он наиболее гибкий, насколько я понял, все состояния обрабатывабтся методами-членами самого класса автомата, это накладывает очень жесткие ограничения на иерархию.
есть ещё что-то вроде паттерна, уверен что на 100% он не подойдет, но, возможно, что-то от него можно будет взять. не знаю, как он называется по-правильному, тут намешано несколько паттернов, но суть такая.
в-общем, мы обворачиваем методы класса, которы часто встречаются в цепочке команд последовательностью "обработчиков" (реализуется через массив observers которые в свою очередь реализуют один стандартный интерфейс). есть "до-обработчики" и "после-обработчики". есть те, которым указано прерывать последовательность операций полностью, и есть те, которые даже в случае неудачи передают управление дальше. обработчики можно включать/выключать и тд динамически, наследовать "таблицы" обработчиков и тд. но в любом случае, мне кажется что для тех задач, о которых тут говорилось, if будет проще, понятнее и быстрее.
 

syfisher

TDD infected!!
Автор оригинала: fisher
имхо слишком абстрактная задача, чтобы получить корректный ответ. думаю, обычные if'ы в конечном итоге в подавляющем большинстве случаев будут быстрее и понятнее мудреных решений...
Да, я тоже понял, что задал слишком абстрактный вопрос. Неохота было описывать ситуацию целиком :).

Автор оригинала: fisher
вариант pacha, может, и забавный, но вряд ли он наиболее гибкий, насколько я понял, все состояния обрабатывабтся методами-членами самого класса автомата, это накладывает очень жесткие ограничения на иерархию.
Как я понял состояние вообще не обрабатываются кроме как на соответствия следующему состоянию (если такое соответствие возможно). Иерархия автоматов здесь, ИМНО, вообще не нужна. Вариант неплохой.


Автор оригинала: fisher
есть ещё что-то вроде паттерна, уверен что на 100% он не подойдет, но, возможно, что-то от него можно будет взять. не знаю, как он называется по-правильному, тут намешано несколько паттернов, но суть такая.
в-общем, мы обворачиваем методы класса, которы часто встречаются в цепочке команд последовательностью "обработчиков" (реализуется через массив observers которые в свою очередь реализуют один стандартный интерфейс). есть "до-обработчики" и "после-обработчики". есть те, которым указано прерывать последовательность операций полностью, и есть те, которые даже в случае неудачи передают управление дальше. обработчики можно включать/выключать и тд динамически, наследовать "таблицы" обработчиков и тд.
Похоже на помесь декоратора, interception filter и цепочки обязанностей. Идея интересная. Только скорее всего ее использование будет неоправданным. Мне хотелось получить гибкий механизм, который бы позволял легко перестраивать набор комманд исходя из конкретного приложения (сайта). Плюс это позволяет нарашивать функциональность на метод как снежный ком, а мне нужна возможность управлять цепочкой комманд.

Да, кстати, ссылки не дашь, где это накопал. Или это свои изыскания? ;)

Автор оригинала: fisher
но в любом случае, мне кажется что для тех задач, о которых тут говорилось, if будет проще, понятнее и быстрее.
Да я тоже склоняюсь к такому мнению, что надо дать возможность разработчику применить более легкие пути решения в данной ситуации (всего не предусмотришь). Вариант pacha, в принципе можно тоже легко расширять путем помещения некоторой логики не в машину состояний, а в сами комманды.

Спасибо за ответы!
 

fisher

накатила суть
Как я понял состояние вообще не обрабатываются кроме как на соответствия следующему состоянию (если такое соответствие возможно). Иерархия автоматов здесь, ИМНО, вообще не нужна. Вариант неплохой.
но кто-то же выполняет команды? я говорил не про иерархию автоматов, я про иерархию классов. но вообще я так подумал - кода слишком мало, чтобы что-то говорить. я ведь что имел в виду. когда есть "неоднородная" цепочка команд, то есть состоящая из команд разного типа (а тут представлен весь спектр MVC), то непонятно, зачем фиксировать все эти переходы по цепочке в рамках одного объекта, какого-то супер-пупер-контроллера. составление правил переходов (регистрацию состояний как в приведенном примере) занимает столько же времени, что и просто "сливание" разных вызовов в условные переходы. они к тому же куда нагляднее.

Да, кстати, ссылки не дашь, где это накопал.
Или это свои изыскания?
свои. да ничего сложного в-общем тут нет, я это дело использовал для реализации объектно-реляционных отображений. только у меня цепочки были короткие, ну простейший пример: есть удаление объекта и надо удалить его из поискового индекса + в зависимости от настроек обновить отношения, которые содержат внешний ключ на него (ну аналог on delete cascade или on delete set null). то есть вся область применения - работа с базой. а если в одну подобную цепочку включать ещё и валидацию форм, и парсинг шаблонов - это будет слишком гремучая смесь, чтобы быть удобной в использовании.
 

syfisher

TDD infected!!
Автор оригинала: fisher
когда есть "неоднородная" цепочка команд, то есть состоящая из команд разного типа (а тут представлен весь спектр MVC), то непонятно, зачем фиксировать все эти переходы по цепочке в рамках одного объекта, какого-то супер-пупер-контроллера.
Супер-пупер :) контроллер нужен в рамках какого-то текущего приложения (сайта). Набор комманд должен быть разным и логика последовательности перевода между ними может меняться от сайта к сайту. Поэтому наследование команд из разным предметных обрастей исключается. То есть команды собираются в кучу каждый раз уникальным образом (ну почти каждый раз ;) )

Автор оригинала: fisher
составление правил переходов (регистрацию состояний как в приведенном примере) занимает столько же времени, что и просто "сливание" разных вызовов в условные переходы. они к тому же куда нагляднее.
Метод pacha имеет достоинство в том, что можно даже написать прогу - визуальный редактор для построения графа состояний и переходов. При условии, что будет известен набор команд и статусов, которые они возвращают.

Автор оригинала: fisher
свои. да ничего сложного в-общем тут нет, я это дело использовал для реализации объектно-реляционных отображений. только у меня цепочки были короткие, ну простейший пример: есть удаление объекта и надо удалить его из поискового индекса + в зависимости от настроек обновить отношения, которые содержат внешний ключ на него (ну аналог on delete cascade или on delete set null). то есть вся область применения - работа с базой. а если в одну подобную цепочку включать ещё и валидацию форм, и парсинг шаблонов - это будет слишком гремучая смесь, чтобы быть удобной в использовании.
В проекте Limb, который пропагандирует pacha, используются похожие interception filters. Они друг в дружку вкладываются (типа того), но никакой переменной логики исполнения нет.
 
Сверху