Конечный автомат UML [1] , ранее известный как диаграмма состояний UML , представляет собой расширение математической концепции конечного автомата в приложениях компьютерной науки , выраженной в нотации унифицированного языка моделирования (UML).
Концепции, лежащие в его основе, касаются организации работы устройства, компьютерной программы или другого (часто технического) процесса таким образом, чтобы сущность или каждая из ее подсущностей всегда находилась ровно в одном из ряда возможных состояний и между этими состояниями существовали четко определенные условные переходы.
Конечный автомат UML — это объектно-ориентированный вариант диаграммы состояний Harel , [2] адаптированный и расширенный UML. [1] [3] Целью конечных автоматов UML является преодоление основных ограничений традиционных конечных автоматов при сохранении их основных преимуществ. Диаграммы состояний UML вводят новые концепции иерархически вложенных состояний и ортогональных областей, расширяя при этом понятие действий. Конечные автоматы UML обладают характеристиками как автоматов Мили , так и автоматов Мура . Они поддерживают действия, зависящие как от состояния системы, так и от события запуска, как в автоматах Мили, а также действия входа и выхода, которые связаны с состояниями, а не с переходами, как в автоматах Мура. [4]
Термин «конечный автомат UML» может относиться к двум видам конечных автоматов: поведенческие конечные автоматы и протокольные конечные автоматы . Поведенческие конечные автоматы могут использоваться для моделирования поведения отдельных сущностей (например, экземпляров классов), подсистемы, пакета или даже целой системы. Конечные автоматы протоколов используются для выражения протоколов использования и могут использоваться для указания допустимых сценариев использования классификаторов, интерфейсов и портов.
Многие программные системы управляются событиями , что означает, что они постоянно ждут возникновения какого-либо внешнего или внутреннего события, такого как щелчок мыши, нажатие кнопки, тик времени или прибытие пакета данных. После распознавания события такие системы реагируют, выполняя соответствующие вычисления, которые могут включать манипулирование оборудованием или генерацию «мягких» событий, которые запускают другие внутренние программные компоненты. (Вот почему системы, управляемые событиями, также называются реактивными системами .) После завершения обработки события система возвращается к ожиданию следующего события.
Реакция на событие обычно зависит как от типа события, так и от внутреннего состояния системы и может включать изменение состояния, приводящее к переходу состояния . Модель событий, состояний и переходов состояний между этими состояниями можно абстрагировать и представить в виде конечного автомата (FSM).
Концепция FSM важна в событийно-управляемом программировании , поскольку она делает обработку событий явно зависящей как от типа события, так и от состояния системы. При правильном использовании конечный автомат может радикально сократить количество путей выполнения через код, упростить условия, проверяемые в каждой точке ветвления, и упростить переключение между различными режимами выполнения. [5] И наоборот, использование событийно-управляемого программирования без базовой модели FSM может привести к тому, что программисты будут создавать подверженный ошибкам, трудно расширяемый и чрезмерно сложный код приложения. [6]
UML сохраняет общую форму традиционных диаграмм состояний . Диаграммы состояний UML представляют собой направленные графы, в которых узлы обозначают состояния, а соединители обозначают переходы состояний. Например, на рисунке 1 показана диаграмма состояний UML, соответствующая конечному автомату клавиатуры компьютера. В UML состояния представлены в виде скругленных прямоугольников, помеченных именами состояний. Переходы, представленные в виде стрелок, помечены запускающими событиями, за которыми, при необходимости, следует список выполненных действий. Начальный переход начинается со сплошного круга и определяет состояние по умолчанию при первом запуске системы. Каждая диаграмма состояний должна иметь такой переход, который не должен быть помечен, поскольку он не запускается событием. Начальный переход может иметь связанные действия.
Событие — это то, что происходит и влияет на систему. Строго говоря, в спецификации UML [1] термин событие относится к типу события, а не к конкретному экземпляру этого события. Например, Нажатие клавиши — это событие для клавиатуры, но каждое нажатие клавиши — это не событие, а конкретный экземпляр события Нажатие клавиши. Другим интересным событием для клавиатуры может быть Включение питания, но включение питания завтра в 10:05:36 будет просто экземпляром события Включение питания.
Событие может иметь связанные параметры , что позволяет экземпляру события передавать не только возникновение какого-то интересного инцидента, но и количественную информацию относительно этого события. Например, событие Keystroke, генерируемое нажатием клавиши на клавиатуре компьютера, имеет связанные параметры, которые передают код сканирования символа, а также статус клавиш Shift, Ctrl и Alt.
Экземпляр события переживает мгновенное событие, которое его сгенерировало, и может передать это событие одному или нескольким конечным автоматам. После генерации экземпляр события проходит жизненный цикл обработки, который может состоять из трех этапов. Сначала экземпляр события принимается, когда он принимается и ожидает обработки (например, помещается в очередь событий ). Позже экземпляр события отправляется в конечный автомат, и в этот момент он становится текущим событием. Наконец, он потребляется , когда конечный автомат завершает обработку экземпляра события. Потребленный экземпляр события больше не доступен для обработки.
Каждый конечный автомат имеет состояние , которое управляет реакцией конечного автомата на события. Например, когда вы нажимаете клавишу на клавиатуре, сгенерированный код символа будет либо заглавным, либо строчным символом, в зависимости от того, активен ли Caps Lock. Таким образом, поведение клавиатуры можно разделить на два состояния: состояние «по умолчанию» и состояние «caps_locked». (Большинство клавиатур включают светодиод, который указывает, что клавиатура находится в состоянии «caps_locked».) Поведение клавиатуры зависит только от определенных аспектов ее истории, а именно, была ли нажата клавиша Caps Lock, но не, например, от того, сколько и какие именно другие клавиши были нажаты ранее. Состояние может абстрагироваться от всех возможных (но не имеющих значения) последовательностей событий и захватывать только соответствующие.
В контексте программных конечных автоматов (и особенно классических FSM) термин состояние часто понимается как одна переменная состояния, которая может принимать только ограниченное количество априори определенных значений (например, два значения в случае клавиатуры или, в более общем смысле, некоторая переменная с типом enum во многих языках программирования ) . Идея переменной состояния (и классической модели FSM) заключается в том, что значение переменной состояния полностью определяет текущее состояние системы в любой момент времени. Концепция состояния сводит проблему идентификации контекста выполнения в коде к тестированию только переменной состояния вместо многих переменных, тем самым устраняя большую часть условной логики.
Однако на практике интерпретация всего состояния конечного автомата как одной переменной состояния быстро становится непрактичной для всех конечных автоматов, кроме очень простых. Действительно, даже если у нас есть одно 32-битное целое число в состоянии нашего автомата, оно может способствовать более чем 4 миллиардам различных состояний - и приведет к преждевременному взрыву состояний . Такая интерпретация непрактична, поэтому в конечных автоматах UML все состояние конечного автомата обычно разделяется на (a) перечислимую переменную состояния и (b) все другие переменные, которые называются расширенным состоянием . Другой способ увидеть это - интерпретировать перечислимую переменную состояния как качественный аспект, а расширенное состояние - как количественные аспекты всего состояния. В этой интерпретации изменение переменной не всегда подразумевает изменение качественных аспектов поведения системы и, следовательно, не приводит к изменению состояния. [7]
Конечные автоматы, дополненные расширенными переменными состояния , называются расширенными конечными автоматами , и конечные автоматы UML относятся к этой категории. Расширенные конечные автоматы могут применять базовый формализм к гораздо более сложным проблемам, чем это практично без включения расширенных переменных состояния. Например, если нам нужно реализовать какой-то предел в нашем FSM (например, ограничить количество нажатий клавиш на клавиатуре до 1000), без расширенного состояния нам нужно будет создать и обработать 1000 состояний, что непрактично; однако с расширенным конечным автоматом мы можем ввести переменную key_count
, которая инициализируется до 1000 и уменьшается при каждом нажатии клавиши без изменения переменной состояния .
Диаграмма состояний на рисунке 2 является примером расширенного конечного автомата, в котором полное состояние системы (называемое расширенным состоянием ) представляет собой комбинацию качественного аспекта — переменной состояния — и количественных аспектов — расширенных переменных состояния .
Очевидное преимущество расширенных конечных автоматов — гибкость. Например, изменение предела, регулируемого key_count
с 1000 до 10000 нажатий клавиш, вообще не усложнит расширенный конечный автомат. Единственной необходимой модификацией будет изменение инициализационного значения переменной key_count
расширенного состояния во время инициализации.
Однако эта гибкость расширенных конечных автоматов имеет свою цену из-за сложной связи между «качественными» и «количественными» аспектами расширенного состояния. Связь осуществляется через охранные условия, прикрепленные к переходам, как показано на рисунке 2.
Условия охраны (или просто охранные) — это логические выражения , динамически оцениваемые на основе значений расширенных переменных состояния и параметров событий. Условия охраны влияют на поведение конечного автомата, разрешая действия или переходы только тогда, когда они оцениваются как TRUE, и запрещая их, когда они оцениваются как FALSE. В нотации UML условия охраны показаны в квадратных скобках (например, [key_count == 0]
на рисунке 2).
Необходимость в охранниках является непосредственным следствием добавления переменных расширенного состояния памяти в формализм конечного автомата. При умеренном использовании переменные расширенного состояния и охранники образуют мощный механизм, который может упростить конструкции. С другой стороны, можно довольно легко злоупотреблять расширенными состояниями и охранниками. [8]
Когда отправляется экземпляр события, конечный автомат реагирует, выполняя действия , такие как изменение переменной, выполнение ввода-вывода, вызов функции, генерация другого экземпляра события или переход в другое состояние. Любые значения параметров, связанные с текущим событием, доступны для всех действий, напрямую вызванных этим событием.
Переключение из одного состояния в другое называется переходом состояния , а событие, которое его вызывает, называется событием запуска или просто триггером . В примере с клавиатурой, если клавиатура находится в состоянии «по умолчанию» при нажатии клавиши CapsLock, клавиатура перейдет в состояние «caps_locked». Однако, если клавиатура уже находится в состоянии «caps_locked», нажатие CapsLock вызовет другой переход — из состояния «caps_locked» в состояние «по умолчанию». В обоих случаях нажатие CapsLock является событием запуска.
В расширенных конечных автоматах переход может иметь охрану, что означает, что переход может «сработать», только если охрана оценивается как ИСТИНА. Состояние может иметь много переходов в ответ на один и тот же триггер, пока у них есть неперекрывающиеся охранные функции; однако эта ситуация может создать проблемы в последовательности оценки охранных функций при возникновении общего триггера. Спецификация UML [1] намеренно не предусматривает какой-либо определенный порядок; вместо этого UML возлагает на проектировщика бремя разработки охранных функций таким образом, чтобы порядок их оценки не имел значения. На практике это означает, что выражения охранных функций не должны иметь побочных эффектов, по крайней мере таких, которые могли бы изменить оценку других охранных функций, имеющих тот же триггер.
Все формализмы конечных автоматов, включая конечные автоматы UML, повсеместно предполагают, что конечный автомат завершает обработку каждого события, прежде чем он сможет начать обработку следующего события. Такая модель выполнения называется запуском до завершения , или RTC.
В модели RTC система обрабатывает события дискретными, неделимыми шагами RTC. Новые входящие события не могут прерывать обработку текущего события и должны сохраняться (обычно в очереди событий ) до тех пор, пока конечный автомат снова не станет бездействующим. Эта семантика полностью избегает любых внутренних проблем параллелизма в пределах одного конечного автомата. Модель RTC также обходит концептуальную проблему обработки действий, связанных с переходами, когда конечный автомат не находится в четко определенном состоянии (находится между двумя состояниями) на протяжении действия. Во время обработки событий система не реагирует (ненаблюдаема), поэтому плохо определенное состояние в течение этого времени не имеет практического значения.
Однако следует отметить, что RTC не означает, что конечный автомат должен монополизировать ЦП до тех пор, пока шаг RTC не будет завершен. [1] Ограничение вытеснения применяется только к контексту задачи конечного автомата, который уже занят обработкой событий. В многозадачной среде могут выполняться другие задачи (не связанные с контекстом задачи занятого конечного автомата), возможно, вытесняя текущий выполняемый конечный автомат. Пока другие конечные автоматы не делятся друг с другом переменными или другими ресурсами, опасности параллелизма отсутствуют .
Ключевое преимущество обработки RTC — простота. Самый большой ее недостаток в том, что отзывчивость конечного автомата определяется его самым длинным шагом RTC. Достижение коротких шагов RTC часто может значительно усложнить проекты реального времени.
Хотя традиционные FSM являются прекрасным инструментом для решения небольших проблем, также общеизвестно, что они имеют тенденцию становиться неуправляемыми, даже для умеренно сложных систем. Из-за явления, известного как взрыв состояний и переходов , сложность традиционного FSM имеет тенденцию расти гораздо быстрее, чем сложность системы, которую он описывает. Это происходит потому, что традиционный формализм конечного автомата вызывает повторения. Например, если вы попытаетесь представить поведение простого карманного калькулятора с помощью традиционного FSM, вы сразу заметите, что многие события (например, нажатия кнопок «Очистить» или «Выключить») обрабатываются одинаково во многих состояниях. Обычный FSM, показанный на рисунке ниже, не имеет средств для захвата такой общности и требует повторения одних и тех же действий и переходов во многих состояниях. Чего не хватает в традиционных конечных автоматах, так это механизма для выделения общего поведения, чтобы разделить его между многими состояниями.
Конечные автоматы UML устраняют именно этот недостаток обычных FSM. Они предоставляют ряд функций для устранения повторений, так что сложность конечного автомата UML больше не взрывается, а стремится точно представлять сложность реактивной системы, которую он описывает. Очевидно, эти функции очень интересны разработчикам программного обеспечения, потому что только они делают подход целого конечного автомата действительно применимым к реальным проблемам.
Самым важным нововведением конечных автоматов UML по сравнению с традиционными FSM является введение иерархически вложенных состояний (именно поэтому диаграммы состояний также называются иерархическими конечными автоматами , или HSM ). Семантика, связанная с вложенностью состояний, следующая (см. Рисунок 3): если система находится во вложенном состоянии, например, «результат» (называемом подсостоянием ), она также (неявно) находится в окружающем состоянии «включено» (называемом суперсостоянием ) . Этот конечный автомат попытается обработать любое событие в контексте подсостояния, которое концептуально находится на нижнем уровне иерархии. Однако, если подсостояние «результат» не предписывает, как обрабатывать событие, событие не отбрасывается тихо, как в традиционном «плоском» конечном автомате; скорее, оно автоматически обрабатывается в контексте более высокого уровня суперсостояния «включено». Это то, что подразумевается под тем, что система находится в состоянии «результат», а также «включено». Конечно, вложенность состояний не ограничивается только одним уровнем, и простое правило обработки событий применяется рекурсивно к любому уровню вложенности.
Состояния, содержащие другие состояния, называются составными состояниями ; наоборот, состояния без внутренней структуры называются простыми состояниями . Вложенное состояние называется прямым подсостоянием , когда оно не содержится ни в каком другом состоянии; в противном случае оно называется транзитивно вложенным подсостоянием .
Поскольку внутренняя структура составного состояния может быть произвольно сложной, любой иерархический конечный автомат можно рассматривать как внутреннюю структуру некоторого (более высокого уровня) составного состояния. Концептуально удобно определить одно составное состояние как конечный корень иерархии конечных автоматов. В спецификации UML каждый конечный автомат имеет область ( абстрактный корень каждой иерархии конечных автоматов), [9] которая содержит все остальные элементы всего конечного автомата. Графическое отображение этой всеохватывающей области необязательно.
Как вы можете видеть, семантика иерархической декомпозиции состояний разработана для облегчения повторного использования поведения. Подсостояния (вложенные состояния) должны только определять отличия от суперсостояний (содержащих состояния). Подсостояние может легко унаследовать [6] общее поведение от своего суперсостояния(й), просто игнорируя обычно обрабатываемые события, которые затем автоматически обрабатываются состояниями более высокого уровня. Другими словами, иерархическая вложенность состояний позволяет программировать по отличию . [10]
Наиболее часто подчеркиваемый аспект иерархии состояний — это абстракция — старый и мощный метод борьбы со сложностью. Вместо того чтобы рассматривать все аспекты сложной системы одновременно, часто можно игнорировать (абстрагироваться) некоторые части системы. Иерархические состояния — идеальный механизм для сокрытия внутренних деталей, поскольку проектировщик может легко уменьшать или увеличивать масштаб, чтобы скрыть или показать вложенные состояния.
Однако составные состояния не просто скрывают сложность; они также активно уменьшают ее посредством мощного механизма иерархической обработки событий. Без такого повторного использования даже умеренное увеличение сложности системы может привести к взрывному увеличению числа состояний и переходов. Например, иерархический конечный автомат, представляющий карманный калькулятор (рисунок 3), избегает повторения переходов Clear и Off практически в каждом состоянии. Избегание повторения позволяет росту HSM оставаться пропорциональным росту сложности системы. По мере роста моделируемой системы возможность повторного использования также увеличивается и, таким образом, потенциально противодействует непропорциональному увеличению числа состояний и переходов, типичному для традиционных FSM.
Анализ путем иерархической декомпозиции состояний может включать применение операции «исключающее ИЛИ» к любому заданному состоянию. Например, если система находится в суперсостоянии «on» (рисунок 3), может быть так, что она также находится в подсостоянии «operand1» ИЛИ в подсостоянии «operand2» ИЛИ в подсостоянии «opEntered» ИЛИ в подсостоянии «result». Это приведет к описанию суперсостояния «on» как «OR-состояния».
UML-диаграммы состояний также вводят комплементарную И-декомпозицию. Такая декомпозиция означает, что составное состояние может содержать две или более ортогональных области (ортогональные в данном контексте означают совместимые и независимые) и что нахождение в таком составном состоянии влечет за собой нахождение во всех его ортогональных областях одновременно. [11]
Ортогональные области решают частую проблему комбинаторного увеличения числа состояний, когда поведение системы фрагментировано на независимые, одновременно активные части. Например, помимо основной клавиатуры, клавиатура компьютера имеет независимую цифровую клавиатуру. Из предыдущего обсуждения вспомним два уже определенных состояния основной клавиатуры: «default» и «caps_locked» (см. рисунок 1). Цифровая клавиатура также может находиться в двух состояниях — «numbers» и «arrows» — в зависимости от того, активен ли Num Lock. Таким образом, полное пространство состояний клавиатуры в стандартной декомпозиции является декартовым произведением двух компонентов (основной клавиатуры и цифровой клавиатуры) и состоит из четырех состояний: «default–numbers», «default–arrows», «caps_locked–numbers» и «caps_locked–arrows». Однако это было бы неестественным представлением, поскольку поведение цифровой клавиатуры не зависит от состояния основной клавиатуры и наоборот. Использование ортогональных областей позволяет избежать смешивания независимых поведений в виде декартова произведения и вместо этого оставить их раздельными, как показано на рисунке 4.
Обратите внимание, что если ортогональные области полностью независимы друг от друга, их объединенная сложность просто аддитивна, что означает, что число независимых состояний, необходимых для моделирования системы, является просто суммой k + l + m + ... , где k, l, m, ... обозначают число OR-состояний в каждой ортогональной области. Однако общий случай взаимной зависимости, с другой стороны, приводит к мультипликативной сложности, поэтому в общем случае число необходимых состояний является произведением k × l × m × ... .
В большинстве реальных ситуаций ортогональные регионы будут лишь приблизительно ортогональны (т.е. не будут по-настоящему независимыми). Поэтому диаграммы состояний UML предоставляют ряд способов для ортогональных регионов сообщаться и синхронизировать свое поведение. Среди этих богатых наборов (иногда сложных) механизмов, возможно, наиболее важной особенностью является то, что ортогональные регионы могут координировать свое поведение, отправляя друг другу экземпляры событий.
Несмотря на то, что ортогональные регионы подразумевают независимость выполнения (допуская большую или меньшую параллельность), спецификация UML не требует, чтобы отдельный поток выполнения был назначен каждому ортогональному региону (хотя это можно сделать при желании). Фактически, чаще всего ортогональные регионы выполняются в одном и том же потоке. [12] Спецификация UML требует только, чтобы проектировщик не полагался на какой-либо конкретный порядок для экземпляров событий, которые будут отправлены в соответствующие ортогональные регионы.
Каждое состояние в диаграмме состояний UML может иметь необязательные действия входа , которые выполняются при входе в состояние, а также необязательные действия выхода , которые выполняются при выходе из состояния. Действия входа и выхода связаны с состояниями, а не с переходами. Независимо от того, как состояние входит или выходит, все его действия входа и выхода будут выполнены. Из-за этой характеристики диаграммы состояний ведут себя как машины Мура . Нотация UML для действий входа и выхода из состояния заключается в размещении зарезервированного слова «вход» (или «выход») в состоянии прямо под отсеком имени, за которым следует косая черта и список произвольных действий (см. рисунок 5).
Ценность действий входа и выхода заключается в том, что они предоставляют средства для гарантированной инициализации и очистки , очень похожие на конструкторы и деструкторы классов в объектно-ориентированном программировании . Например, рассмотрим состояние "door_open" на рисунке 5, которое соответствует поведению тостера, пока дверца открыта. Это состояние имеет очень важное критическое для безопасности требование: всегда отключать нагреватель, когда дверца открыта. Кроме того, пока дверца открыта, внутренняя лампа, освещающая духовку, должна гореть.
Конечно, такое поведение можно смоделировать, добавив соответствующие действия (отключение нагревателя и включение света) к каждому пути перехода, ведущему к состоянию "door_open" (пользователь может открыть дверь в любой момент во время "выпечки" или "поджаривания" или когда духовка вообще не используется). Не следует забывать гасить внутреннюю лампу при каждом переходе, покидающем состояние "door_open". Однако такое решение приведет к повторению действий во многих переходах. Что еще важнее, такой подход оставляет конструкцию подверженной ошибкам при последующих изменениях поведения (например, следующий программист, работающий над новой функцией, такой как поджаривание сверху, может просто забыть отключить нагреватель при переходе в "door_open").
Действия входа и выхода позволяют реализовать желаемое поведение более безопасным, простым и интуитивно понятным способом. Как показано на рисунке 5, можно указать, что действие выхода из «нагрева» отключает нагреватель, действие входа в «открытие двери» зажигает лампу духовки, а действие выхода из «открытия двери» гасит лампу. Использование действий входа и выхода предпочтительнее, чем размещение действия на переходе, поскольку это позволяет избежать повторяющегося кодирования и улучшает функцию, устраняя угрозу безопасности; (нагреватель включен, пока дверь открыта). Семантика действий выхода гарантирует, что независимо от пути перехода нагреватель будет отключен, когда тостер не находится в состоянии «нагрева».
Поскольку действия входа выполняются автоматически при каждом входе в связанное состояние, они часто определяют условия работы или идентичность состояния, во многом так же, как конструктор класса определяет идентичность конструируемого объекта. Например, идентичность состояния «нагревание» определяется тем фактом, что нагреватель включен. Это условие должно быть установлено до входа в любое подсостояние «нагревание», поскольку действия входа в подсостояние «нагревание», например «поджаривание», полагаются на правильную инициализацию суперсостояния «нагревание» и выполняют только отличия от этой инициализации. Следовательно, порядок выполнения действий входа всегда должен идти от самого внешнего состояния к самому внутреннему (сверху вниз).
Неудивительно, что этот порядок аналогичен порядку вызова конструкторов классов. Построение класса всегда начинается с самого корня иерархии классов и проходит через все уровни наследования вплоть до создаваемого класса. Выполнение действий выхода, которое соответствует вызову деструктора, происходит в точно обратном порядке (снизу вверх).
Очень часто событие вызывает выполнение только некоторых внутренних действий, но не приводит к изменению состояния (переходу состояния). В этом случае все выполненные действия составляют внутренний переход . Например, когда кто-то печатает на клавиатуре, она реагирует, генерируя различные коды символов. Однако, если не нажата клавиша Caps Lock, состояние клавиатуры не меняется (переход состояния не происходит). В UML эта ситуация должна моделироваться с помощью внутренних переходов, как показано на рисунке 6. Нотация UML для внутренних переходов следует общему синтаксису, используемому для действий выхода (или входа), за исключением того, что вместо слова вход (или выход) внутренний переход помечен событием-триггером (например, см. внутренний переход, вызванный событием ANY_KEY на рисунке 6).
При отсутствии действий входа и выхода внутренние переходы были бы идентичны самопереходам (переходам, в которых целевое состояние совпадает с исходным состоянием). Фактически, в классической машине Мили действия связаны исключительно с переходами состояний, поэтому единственный способ выполнить действия без изменения состояния — это самопереход (изображенный как направленный цикл на рисунке 1 в верхней части этой статьи). Однако при наличии действий входа и выхода, как в диаграммах состояний UML, самопереход включает в себя выполнение действий выхода и входа и, следовательно, он существенно отличается от внутреннего перехода.
В отличие от самоперехода, в результате внутреннего перехода никогда не выполняются действия входа или выхода, даже если внутренний переход унаследован от более высокого уровня иерархии, чем текущее активное состояние. Внутренние переходы, унаследованные от суперсостояний на любом уровне вложенности, действуют так, как если бы они были определены непосредственно в текущем активном состоянии.
Вложенность состояний в сочетании с действиями входа и выхода значительно усложняет семантику перехода состояний в HSM по сравнению с традиционными FSM. При работе с иерархически вложенными состояниями и ортогональными областями простой термин текущее состояние может быть довольно запутанным. В HSM одновременно может быть активным более одного состояния. Если конечный автомат находится в листовом состоянии, которое содержится в составном состоянии (которое, возможно, содержится в составном состоянии более высокого уровня и т. д.), все составные состояния, которые напрямую или транзитивно содержат листовое состояние, также активны. Более того, поскольку некоторые из составных состояний в этой иерархии могут иметь ортогональные области, текущее активное состояние фактически представлено деревом состояний, начинающимся с единственного региона в корне и заканчивающимся отдельными простыми состояниями в листьях. Спецификация UML называет такое дерево состояний конфигурацией состояний. [1]
В UML переход состояния может напрямую соединять любые два состояния. Эти два состояния, которые могут быть составными, обозначены как основной источник и основная цель перехода. На рисунке 7 показан простой пример перехода и объяснены роли состояний в этом переходе. Спецификация UML предписывает, что переход состояния подразумевает выполнение действий в следующей предопределенной последовательности (см. раздел 14.2.3.9.6 OMG Unified Modeling Language (OMG UML) [1] ):
Последовательность перехода легко интерпретировать в простом случае, когда основной источник и основная цель вложены на одном уровне. Например, переход T1, показанный на рисунке 7, вызывает оценку охранника g();, за которой следует последовательность действий: a(); b(); t(); c(); d();
и e()
;, предполагая, что охранник g()
оценивается как ИСТИНА.
Однако в общем случае исходных и целевых состояний, вложенных на разных уровнях иерархии состояний, может быть не сразу очевидно, из скольких уровней вложенности необходимо выйти. Спецификация UML [1] предписывает, что переход включает выход из всех вложенных состояний из текущего активного состояния (которое может быть прямым или транзитивным подсостоянием основного исходного состояния) до, но не включая, состояния наименьшего общего предка (LCA) основного исходного и основного целевого состояний. Как следует из названия, LCA является самым низким составным состоянием, которое одновременно является суперсостоянием (предком) как исходного, так и целевого состояний. Как было описано ранее, порядок выполнения действий выхода всегда от наиболее глубоко вложенного состояния (текущего активного состояния) вверх по иерархии к LCA, но без выхода из LCA. Например, LCA(s1,s2) состояний "s1" и "s2", показанное на рисунке 7, является состоянием "s".
Вход в конфигурацию целевого состояния начинается с уровня, на котором остановились действия выхода (т. е. изнутри LCA). Как было описано ранее, действия входа должны выполняться, начиная с состояния самого высокого уровня вниз по иерархии состояний до основного целевого состояния. Если основное целевое состояние является составным, семантика UML предписывает «вскрывать» его подмашину рекурсивно, используя локальные начальные переходы. Конфигурация целевого состояния полностью вводится только после встречи с конечным состоянием, не имеющим начальных переходов.
До UML 2 [1] единственной используемой семантикой перехода был внешний переход , при котором основной источник перехода всегда выходит, а основная цель перехода всегда входит. UML 2 сохранил семантику «внешнего перехода» для обратной совместимости, но также ввел новый вид перехода, называемый локальным переходом (см. раздел 14.2.3.4.4 Unified Modeling Language (UML) [1] ). Для многих топологий перехода внешние и локальные переходы фактически идентичны. Однако локальный переход не вызывает выхода из основного исходного состояния и повторного входа в него, если основное целевое состояние является подсостоянием основного источника. Кроме того, локальный переход состояния не вызывает выхода из основного целевого состояния и повторного входа в него, если основное целевое состояние является суперсостоянием основного исходного состояния.
Рисунок 8 противопоставляет локальные (a) и внешние (b) переходы. В верхнем ряду вы видите случай основного источника, содержащего основную цель. Локальный переход не вызывает выход из источника, тогда как внешний переход вызывает выход и повторный вход в источник. В нижнем ряду рисунка 8 вы видите случай основной цели, содержащей основной источник. Локальный переход не вызывает вход в цель, тогда как внешний переход вызывает выход и повторный вход в цель.
Иногда событие происходит в особенно неудобное время, когда конечный автомат находится в состоянии, которое не может обработать событие. Во многих случаях природа события такова, что его можно отложить (в определенных пределах) до тех пор, пока система не перейдет в другое состояние, в котором она будет лучше подготовлена к обработке исходного события.
Конечные автоматы UML предоставляют специальный механизм для отсрочки событий в состояниях. В каждом состоянии можно включить предложение [event list]/defer
. Если происходит событие в списке отложенных событий текущего состояния, событие будет сохранено (отложено) для будущей обработки до тех пор, пока не будет введено состояние, в списке отложенных событий которого нет этого события. При входе в такое состояние конечный автомат UML автоматически вызовет все сохраненные события, которые больше не откладываются, а затем либо потребит, либо отменит эти события. Для суперсостояния возможен переход, определенный для события, отложенного подсостоянием. В соответствии с другими областями в спецификации конечных автоматов UML подсостояние имеет приоритет над суперсостоянием, событие будет отложено, а переход для суперсостояния не будет выполнен. В случае ортогональных регионов, где один ортогональный регион откладывает событие, а другой потребляет его, потребитель имеет приоритет, и событие потребляется, а не откладывается.
Диаграммы состояний Харела, которые являются предшественниками конечных автоматов UML, были изобретены как «визуальный формализм для сложных систем» [2] , поэтому с самого начала они были неразрывно связаны с графическим представлением в форме диаграмм состояний. Однако важно понимать, что концепция конечного автомата UML выходит за рамки любой конкретной нотации, графической или текстовой. Спецификация UML [1] делает это различие очевидным, четко разделяя семантику конечного автомата от нотации.
Однако нотация диаграмм состояний UML не является чисто визуальной. Любой нетривиальный конечный автомат требует большого количества текстовой информации (например, спецификации действий и защит). Точный синтаксис выражений действий и защит не определен в спецификации UML, поэтому многие используют либо структурированный английский, либо, более формально, выражения на языке реализации, таком как C , C++ или Java . [13] На практике это означает, что нотация диаграмм состояний UML сильно зависит от конкретного языка программирования .
Тем не менее, большинство семантик диаграмм состояний сильно смещены в сторону графической нотации. Например, диаграммы состояний плохо представляют последовательность обработки, будь то порядок оценки защитных устройств или порядок отправки событий в ортогональные регионы. Спецификация UML обходит эти проблемы, возлагая на проектировщика бремя не полагаться на какую-либо конкретную последовательность. Однако, когда конечные автоматы UML фактически реализуются, неизбежно существует полный контроль над порядком выполнения, что порождает критику того, что семантика UML может быть излишне ограничительной. Аналогично, диаграммы диаграмм состояний требуют много сантехнического оборудования (псевдосостояний, таких как соединения, разветвления, соединения, точки выбора и т. д.) для графического представления потока управления. Другими словами, эти элементы графической нотации не добавляют большой ценности в представлении потока управления по сравнению с простым структурированным кодом .
Нотация и семантика UML действительно ориентированы на компьютеризированные инструменты UML . Конечный автомат UML, представленный в инструменте, — это не просто диаграмма состояний, а скорее смесь графического и текстового представления, которая точно отражает как топологию состояний, так и действия. Пользователи инструмента могут получить несколько дополнительных представлений одного и того же конечного автомата, как визуальных, так и текстовых, тогда как сгенерированный код — это всего лишь одно из многих доступных представлений.