Author: | Евгений Охотников |
---|---|
Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
Version: | 0.1 |
Date: | 2008.03.11 |
Одной из ключевых особенностей SObjectizer является то, что если событие не разрешено к обработке в текущем состоянии агента, то это событие просто игнорируется и никаких действий не происходит. Например, пусть есть агент, который обладает двумя состояниями: st_closed и st_open. В состоянии st_closed агент обрабатывает события evt_open, evt_timeout, а остальные события игнорирует. В состоянии st_open обрабатываются события evt_disconnect, evt_read и evt_write, событие evt_timeout игнорируется, а события evt_open не должно быть в принципе. В SObjectizer 4 такой агент описывается следующим образом:
SOL4_CLASS_START( a_my_agent ) SOL4_EVENT( evt_open ) SOL4_EVENT( evt_disconnect ) SOL4_EVENT( evt_timeout ) SOL4_EVENT( evt_read ) SOL4_EVENT( evt_write ) SOL4_STATE_START( st_closed ) SOL4_STATE_EVENT( evt_open ) SOL4_STATE_EVENT( evt_timeout ) SOL4_STATE_FINISH() SOL4_STATE_START( st_open ) SOL4_STATE_EVENT( evt_disconnect ) SOL4_STATE_EVENT( evt_read ) SOL4_STATE_EVENT( evt_write ) SOL4_STATE_FINISH() SOL4_CLASS_FINISH()
Т.е. при описании состояния программист должен перечислить все разрешенные в данном состоянии события.
Такой подход основывается на предположении, что программист понимает, что он делает. И главное достоинство этого подхода в том, что здесь требуется минимальный набор описаний для состояния агента.
К сожалению, в подобном подходе к описанию состояний нельзя проконтролировать корректность сделанных пользователем описаний. А программисты имеют свойство ошибаться. Можно просто забыть описать событие в списке разрешенных событий состояния, что особенно плохо при сопровождении ранее написанного кем-то агента, когда агент расширяется новыми сообщениями, событиями или состояниями. Так же можно не сообразить, что какое-то событие может инициироваться в некотором состоянии агента.
Описываемое здесь предложение состоит в том, чтобы заставить программиста явно указывать, что должно происходить со всеми событиями в каждом из состояний агентов. Существует надежда на то, что подобный жесткий подход поможет решению подобных проблем за счет того, что:
Для того, чтобы эти предположения оправдались необходимо делать описание класса агента, которое затем обрабатывается каким-то транслятором. Этот транслятор следит за тем, чтобы все события были определены в каждом из состояний. Если это условие нарушается, то транслятор отклоняет описание класса агента.
В описываемых ниже вариантах используется описание класса агента в виде Ruby-скрипта (вообще-то конкретный формат описания не имеет значения, Ruby выбран как наиболее удобный для автора вариант).
Это самый простой вариант, в котором программиста заставляют перечислять все события в каждом из состояний, с указанием того, разрешено ли событие к обработке или нет.
Например, для обсуждаемого здесь примера описание класса агента могло бы иметь вид:
agent_class :a_my_agent do |c| ... c.state :st_closed do |st| # Перечисление разрешенных событий. st.enabled :evt_open, :evt_timeout # Перечисление запрещенных событий. st.disabled :evt_disconnect, :evt_read, :evt_write end c.state :st_open do |st| st.enabled :evt_disconnect, :evt_read, :evt_write st.disabled :evt_open, :evt_timeout end end
Практически, этот вариант является ничем иным, как простым расширением использующегося сейчас в SObjectizer 4 подхода.
Данный вариант основывается на том, что событие является всего лишь следствием возникновения сообщения-инцидента. И что в первую очередь при разработке агента нужно сосредоточиться на том, какие сообщения в каком из состояний обрабатываются. Т.е., нужно описывать, какие сообщения в каком состоянии к какому событию приводят.
Описание обсуждаемого здесь класса агента может выглядеть следующим образом:
agent_class :a_my_agent do |c| c.msg_slot :msg_connected c.msg_slot :msg_disconnected c.msg_slot :msg_timeout c.msg_slot :msg_write c.msg_slot :msg_read c.state :st_closed do |st| st.on :msg_connected => :evt_open st.on :msg_disconnected => UNEXPECTED st.on :msg_timeout => :evt_timeout st.on :msg_read => IGNORE st.on :msg_write => IGNORE end c.state :st_open do |st| st.on :msg_connected => UNEXPECTED st.on :msg_disconnected => :evt_disconnect st.on :msg_timeout => IGNORE st.on :msg_read => :evt_read st.on :msg_write => :evt_write end end
Данное описание говорит о том, что:
При таком подходе возможно достижение еще одного побочного эффекта: в описании класса агента не нужно отдельно описывать события агента и типы их инцидентов. Эту информацию можно вывести исходя из описаний состояний агента, т.к. в них будут перечисленны все события, которыми владеет агент.
Описание:
agent_class :a_my_agent do |c| c.msg_slot :msg_connected ... end
говорит о том, что описывается не само сообщение, а т.н. слот сообщения. Слот -- это аналог переменной, которая во время работы агента будет содержать имя реального сообщения.
Понятие слота введено из-за того, что не все сообщения могут быть известны для агента в момент трансляции описания его класса. Например, подписка агента (т.е. имена сообщений-инцидентов) определяется непосредственно во время работы по каким-то алгоритмам или параметрам конфигурации. В этом случае слот должен быть определен во время работы агента. При этом агент определяет значение своего слота, а SObjectizer автоматически обеспечивает подписку заинтересованных в этом слоте событий. Т.е. в таком варианте программист должен определять слоты сообщений, а не подписывать события агента, как в текущей версии SObjectizer 4.
Если же значение слота сообщения известно еще до запуска агента (например, агент подписывается только на собственные сообщения), то этот факт можно отразить непосредственно в описании слота. Например:
agent_class :a_my_agent do |c| c.msg_slot :msg_connected, own_message c.msg_slot :msg_disconnected, own_message( :fields => { :reason => STRING } ) ... end
Такое описание говорит о том, что слот msg_connected будет связан с сообщением msg_connected, которым владеет сам агент типа a_my_agent. Сообщение msg_connected не имеет доступных для SOP полей. А вот сообщение msg_disconnected того же агента, связанное со слотом msg_disconnected, имеет строковое поле reason, которое доступно для SOP. Т.е. эти описания эквивалентны описаниями в SObjectizer 4:
SOL4_CLASS_START( a_my_agent ) SOL4_MSG_START( msg_connected, a_my_agent::msg_connected ) SOL4_MSG_FINISH() SOL4_MSG_START( msg_disconnected, a_my_agent::msg_disconnected ) SOL4_MSG_FIELD( reason ) SOL4_MSG_FINISH() ... SOL4_CLASS_FINISH()
Для демонстрации описанных выше вариантов решения проблемы был взят один из самых больших агентов, которые приходилось разрабатывать автору. Для него были сделаны описания в соотвествии с каждым из предложенных вариантов.
Исходное описание агента в SObjectizer 4 (244 строки).
Описание агента с помощью первого варианта (318 строк).
Описание агента с помощью второго варианта (246 строк).
По мнению автора, описание с помощью второго варианта более информативно, чем два других описания, посколько оно позволяет легко определить:
Но при этом само описание по объему практически равно первоначальному описанию с помощью нотации SObjectizer 4.
По замыслу, необходимость строго перечисления состояний/сообщений в каждом из событий агента должно играть такую же роль, как контроль pattern matching в функциональных языках программирования со стороны компилятора: компилятор в состоянии указать программисту о том, что не все варианты сопоставления с образцом программист определил. А во время исполнения программы стандартная библиотека порождает исключение, если в pattern matching-выражении ни один из вариантов не сработал. В сумме эти два свойства pattern matching-а положительно сказываются на качестве программы.
Предложенные варианты является лишь первыми шагами в направлении решения проблемы забытых/непредусмотренных событий в состояниях агентов. Возможно, не самыми удачными шагами. Например, не очень понятно, во что выльется подобный подход для больших агентов и для наследования агентов. Тем не менее, он может стать прообразом более удачного решения, которое и будет воплощено в одной из следующих версий SObjectizer.