La máquina de estados UML , [ 1 ] anteriormente conocida como diagrama de estados UML , es una extensión del concepto matemático de un autómata finito en aplicaciones de ciencias de la computación, tal como se expresa en la notación del Lenguaje Unificado de Modelado (UML).
Los conceptos en los que se basa tratan de organizar el funcionamiento de un dispositivo, un programa informático u otro proceso (a menudo técnico) de tal manera que una entidad o cada una de sus subentidades se encuentre siempre en uno de varios estados posibles y donde existan transiciones condicionales bien definidas entre estos estados.
La máquina de estados UML es una variante basada en objetos del diagrama de estados de Harel , [ 2 ] adaptada y extendida por UML. [ 1 ] [ 3 ] El objetivo de las máquinas de estados UML es superar las principales limitaciones de las máquinas de estados finitos tradicionales , conservando sus principales ventajas. Los diagramas de estados UML introducen los nuevos conceptos de estados jerárquicamente anidados y regiones ortogonales , al tiempo que extienden la noción de acciones . Las máquinas de estados UML tienen las características tanto de las máquinas de Mealy como de las máquinas de Moore . Admiten acciones que dependen tanto del estado del sistema como del evento desencadenante , como en las máquinas de Mealy, así como acciones de entrada y salida , que están asociadas a estados en lugar de transiciones, como en las máquinas de Moore. [ 4 ]
El término "máquina de estados UML" puede referirse a dos tipos: máquinas de estados de comportamiento y máquinas de estados de protocolo . Las máquinas de estados de comportamiento se utilizan para modelar el comportamiento de entidades individuales (por ejemplo, instancias de clase), un subsistema, un paquete o incluso un sistema completo. Las máquinas de estados de protocolo se utilizan para expresar protocolos de uso y para especificar los escenarios de uso válidos de clasificadores, interfaces y puertos.
Conceptos básicos de máquinas de estados
Muchos sistemas de software se basan en eventos , lo que significa que esperan continuamente a que ocurra algún evento externo o interno , como un clic del ratón, la pulsación de un botón, un temporizador o la llegada de un paquete de datos. Tras reconocer el evento, estos sistemas reaccionan realizando los cálculos pertinentes, que pueden incluir la manipulación del hardware o la generación de eventos "suaves" que activan otros componentes internos del software. (Por eso, a los sistemas basados en eventos también se les denomina sistemas reactivos ). Una vez finalizado el procesamiento del evento, el sistema vuelve a esperar el siguiente.
La respuesta a un evento generalmente depende tanto del tipo de evento como del estado interno del sistema, y puede incluir un cambio de estado que conduzca a una transición de estado . El patrón de eventos, estados y transiciones de estado entre esos estados se puede abstraer y representar como una máquina de estados finitos (MEF).
El concepto de máquina de estados finitos (FSM) es importante en la programación orientada a eventos porque hace que el manejo de eventos dependa explícitamente tanto del tipo de evento como del estado del sistema. Cuando se usa correctamente, una máquina de estados puede reducir drásticamente el número de rutas de ejecución a través del código, simplificar las condiciones que se prueban en cada punto de bifurcación y simplificar el cambio entre diferentes modos de ejecución. [ 5 ] Por el contrario, usar la programación orientada a eventos sin un modelo FSM subyacente puede llevar a los programadores a producir código de aplicación propenso a errores, difícil de extender y excesivamente complejo. [ 6 ]
Diagramas de estados UML básicos
UML conserva la forma general de los diagramas de estado tradicionales . Los diagramas de estado UML son grafos dirigidos en los que los nodos representan estados y los conectores, transiciones de estado. Por ejemplo, la Figura 1 muestra un diagrama de estado UML correspondiente a la máquina de estados del teclado de la computadora. En UML, los estados se representan como rectángulos redondeados etiquetados con nombres de estado. Las transiciones, representadas como flechas, se etiquetan con los eventos desencadenantes, seguidos opcionalmente por la lista de acciones ejecutadas. La transición inicial se origina en el círculo sólido y especifica el estado predeterminado cuando el sistema se inicia. Todo diagrama de estado debe tener una transición de este tipo, que no debe estar etiquetada, ya que no se activa por ningún evento. La transición inicial puede tener acciones asociadas.

Eventos
Un evento es algo que sucede y afecta al sistema. Estrictamente hablando, en la especificación UML, [ 1 ] el término evento se refiere al tipo de ocurrencia, no a una instancia concreta de dicha ocurrencia. Por ejemplo, Keystroke es un evento para el teclado, pero cada pulsación de una tecla no es un evento, sino una instancia concreta del evento Keystroke. Otro evento de interés para el teclado podría ser Power-on, pero encenderlo mañana a las 10:05:36 será simplemente una instancia del evento Power-on.
Un evento puede tener parámetros asociados , lo que permite que la instancia del evento transmita no solo la ocurrencia de algún incidente interesante, sino también información cuantitativa sobre dicho incidente. Por ejemplo, el evento Keystroke, generado al presionar una tecla en un teclado de computadora, tiene parámetros asociados que transmiten el código de escaneo de caracteres, así como el estado de las teclas Shift, Ctrl y Alt.
Una instancia de evento sobrevive al suceso instantáneo que la generó y puede transmitir dicho suceso a una o más máquinas de estado. Una vez generada, la instancia de evento pasa por un ciclo de procesamiento que puede constar de hasta tres etapas. Primero, la instancia de evento se recibe cuando se acepta y queda a la espera de procesamiento (por ejemplo, se coloca en la cola de eventos ). Posteriormente, la instancia de evento se envía a la máquina de estado, momento en el que se convierte en el evento actual. Finalmente, se consume cuando la máquina de estado termina de procesar la instancia de evento. Una instancia de evento consumida ya no está disponible para su procesamiento.
Estados
Cada máquina de estados tiene un estado que rige su reacción ante los eventos. Por ejemplo, al pulsar una tecla del teclado, el código de carácter generado será una mayúscula o una minúscula, dependiendo de si la tecla Bloq Mayús está activa. Por lo tanto, el comportamiento del teclado se puede dividir en dos estados: el estado "predeterminado" y el estado "Bloq Mayús activado". (La mayoría de los teclados incluyen un LED que indica que el teclado está en el estado "Bloq Mayús activado"). El comportamiento de un teclado depende únicamente de ciertos aspectos de su historial, concretamente de si se ha pulsado la tecla Bloq Mayús, pero no, por ejemplo, de cuántas teclas se han pulsado previamente ni cuáles exactamente. Un estado puede abstraer todas las secuencias de eventos posibles (pero irrelevantes) y capturar solo las relevantes.
En el contexto de las máquinas de estados de software (y especialmente de las máquinas de estados finitos clásicas), el término " estado" se suele entender como una única variable de estado que solo puede asumir un número limitado de valores predeterminados (por ejemplo, dos valores en el caso del teclado, o, de forma más general, algún tipo de variable con un tipo enumerado en muchos lenguajes de programación). La idea de la variable de estado (y del modelo clásico de máquina de estados finitos) es que su valor define completamente el estado actual del sistema en cualquier momento dado. El concepto de estado reduce el problema de identificar el contexto de ejecución en el código a comprobar únicamente la variable de estado en lugar de muchas variables, eliminando así gran parte de la lógica condicional.
Estados extendidos
En la práctica, sin embargo, interpretar el estado completo de la máquina de estados como una única variable de estado se vuelve rápidamente impracticable para todas las máquinas de estados que no sean muy simples. De hecho, incluso si tenemos un único entero de 32 bits en el estado de nuestra máquina, podría contribuir a más de 4 mil millones de estados diferentes, lo que provocaría una explosión prematura de estados . Esta interpretación no es práctica, por lo que en las máquinas de estados UML el estado completo de la máquina de estados se divide comúnmente en (a) una variable de estado enumerable y (b) todas las demás variables que se denominan estado extendido . Otra forma de verlo es interpretar la variable de estado enumerable como un aspecto cualitativo y el estado extendido como aspectos cuantitativos del estado completo. En esta interpretación, un cambio de variable no siempre implica un cambio en los aspectos cualitativos del comportamiento del sistema y, por lo tanto, no conduce a un cambio de estado. [ 7 ]
Las máquinas de estados complementadas con variables de estado extendidas se denominan máquinas de estados extendidas , y las máquinas de estados UML pertenecen a esta categoría. Las máquinas de estados extendidas pueden aplicar el formalismo subyacente a problemas mucho más complejos de lo que sería práctico sin incluir variables de estado extendidas. Por ejemplo, si tenemos que implementar algún tipo de límite en nuestra máquina de estados finitos (por ejemplo, limitar el número de pulsaciones de teclas en el teclado a 1000), sin un estado extendido necesitaríamos crear y procesar 1000 estados, lo cual no es práctico; sin embargo, con una máquina de estados extendida podemos introducir una key_countvariable que se inicializa a 1000 y se decrementa con cada pulsación de tecla sin cambiar la variable de estado .

El diagrama de estados de la Figura 2 es un ejemplo de una máquina de estados extendida, en la que el estado completo del sistema (llamado estado extendido ) es la combinación de un aspecto cualitativo (la variable de estado ) y los aspectos cuantitativos (las variables de estado extendidas ).
La ventaja evidente de las máquinas de estados extendidas es su flexibilidad. Por ejemplo, cambiar el límite de key_countpulsaciones de tecla de 1000 a 10000 no complicaría en absoluto la máquina de estados extendida. La única modificación necesaria sería cambiar el valor de inicialización de la key_countvariable de estado extendida durante la inicialización.
Esta flexibilidad de las máquinas de estados extendidos tiene un precio, sin embargo, debido al complejo acoplamiento entre los aspectos cualitativos y cuantitativos del estado extendido. El acoplamiento se produce a través de las condiciones de guarda asociadas a las transiciones, como se muestra en la Figura 2.
Condiciones de vigilancia
Las condiciones de guarda (o simplemente guardas) son expresiones booleanas que se evalúan dinámicamente en función del valor de las variables de estado extendidas y los parámetros de evento . Las condiciones de guarda afectan el comportamiento de una máquina de estados, habilitando acciones o transiciones solo cuando se evalúan como VERDADERAS y deshabilitándolas cuando se evalúan como FALSAS. En la notación UML, las condiciones de guarda se muestran entre corchetes (por ejemplo, [key_count == 0]en la Figura 2).
La necesidad de usar guardas es la consecuencia inmediata de añadir variables de estado extendidas con memoria al formalismo de la máquina de estados. Usadas con moderación, las variables de estado extendidas y las guardas conforman un mecanismo potente que puede simplificar los diseños. Por otro lado, es posible abusar de los estados extendidos y las guardas con bastante facilidad. [ 8 ]
Acciones y transiciones
Cuando se activa una instancia de evento , la máquina de estados responde realizando acciones , como modificar una variable, realizar operaciones de entrada/salida, invocar una función, generar otra instancia de evento o cambiar de estado. Los valores de los parámetros asociados al evento actual están disponibles para todas las acciones directamente derivadas de dicho evento.
El cambio de un estado a otro se denomina transición de estado , y el evento que la provoca se llama evento desencadenante o simplemente disparador . En el ejemplo del teclado, si este se encuentra en el estado "predeterminado" al pulsar la tecla Bloq Mayús, pasará al estado "bloqueado en mayúsculas". Sin embargo, si el teclado ya está en el estado "bloqueado en mayúsculas", al pulsar Bloq Mayús se producirá una transición diferente: del estado "bloqueado en mayúsculas" al estado "predeterminado". En ambos casos, pulsar Bloq Mayús es el evento desencadenante.
En las máquinas de estados extendidas , una transición puede tener una condición de guarda , lo que significa que la transición solo puede "activarse" si la condición de guarda se evalúa como VERDADERA. Un estado puede tener varias transiciones en respuesta al mismo disparador, siempre que tengan condiciones de guarda que no se solapen; sin embargo, esta situación podría crear problemas en la secuencia de evaluación de las condiciones de guarda cuando se produce el disparador común. La especificación UML [ 1 ] no estipula intencionadamente ningún orden en particular; más bien, UML impone al diseñador la responsabilidad de diseñar las condiciones de guarda de tal manera que el orden de su evaluación no importe. En la práctica, esto significa que las expresiones de las condiciones de guarda no deberían tener efectos secundarios, al menos ninguno que altere la evaluación de otras condiciones de guarda que tengan el mismo disparador.
Modelo de ejecución de ejecución completa
Todos los formalismos de máquinas de estados, incluidas las máquinas de estados UML, asumen universalmente que una máquina de estados completa el procesamiento de cada evento antes de poder comenzar a procesar el siguiente. Este modelo de ejecución se denomina ejecución hasta su finalización (RTC, por sus siglas en inglés).
En el modelo RTC, el sistema procesa los eventos en pasos RTC discretos e indivisibles. Los nuevos eventos entrantes no pueden interrumpir el procesamiento del evento actual y deben almacenarse (normalmente en una cola de eventos ) hasta que la máquina de estados vuelva a estar inactiva. Esta semántica evita por completo cualquier problema de concurrencia interna dentro de una misma máquina de estados. El modelo RTC también resuelve el problema conceptual del procesamiento de acciones asociadas a transiciones, donde la máquina de estados no se encuentra en un estado bien definido (está entre dos estados) durante la duración de la acción. Durante el procesamiento de eventos, el sistema no responde (no es observable), por lo que el estado mal definido durante ese tiempo carece de relevancia práctica.
Sin embargo, tenga en cuenta que RTC no significa que una máquina de estados deba monopolizar la CPU hasta que se complete el paso RTC. [ 1 ] La restricción de expropiación solo se aplica al contexto de tarea de la máquina de estados que ya está ocupada procesando eventos. En un entorno multitarea , otras tareas (no relacionadas con el contexto de tarea de la máquina de estados ocupada) pueden estar ejecutándose, posiblemente expropiando la máquina de estados que se está ejecutando actualmente. Siempre que otras máquinas de estados no compartan variables u otros recursos entre sí, no hay riesgos de concurrencia .
La principal ventaja del procesamiento RTC es su simplicidad. Su mayor desventaja radica en que la capacidad de respuesta de una máquina de estados está determinada por su paso RTC más largo. Lograr pasos RTC cortos suele complicar significativamente los diseños en tiempo real.
Extensiones de UML al formalismo tradicional de máquinas de estados finitos
Si bien las máquinas de estados finitos (FSM) tradicionales son una excelente herramienta para abordar problemas pequeños, también es sabido que tienden a volverse inmanejables, incluso para sistemas moderadamente complejos. Debido al fenómeno conocido como explosión de estados y transiciones , la complejidad de una FSM tradicional tiende a crecer mucho más rápido que la complejidad del sistema que describe. Esto sucede porque el formalismo de las máquinas de estados tradicionales impone repeticiones. Por ejemplo, si intenta representar el comportamiento de una calculadora de bolsillo simple con una FSM tradicional, notará de inmediato que muchos eventos (por ejemplo, las pulsaciones de los botones Borrar o Apagar) se manejan de forma idéntica en muchos estados. Una FSM convencional, como la que se muestra en la figura siguiente, no tiene forma de capturar dicha similitud y requiere repetir las mismas acciones y transiciones en muchos estados. Lo que falta en las máquinas de estados tradicionales es el mecanismo para factorizar el comportamiento común y así compartirlo entre muchos estados.

Las máquinas de estados UML abordan precisamente esta deficiencia de las máquinas de estados finitos convencionales. Proporcionan diversas características para eliminar las repeticiones, de modo que la complejidad de una máquina de estados UML no se dispara, sino que tiende a representar fielmente la complejidad del sistema reactivo que describe. Obviamente, estas características son muy interesantes para los desarrolladores de software, ya que solo ellas hacen que el enfoque de las máquinas de estados sea verdaderamente aplicable a problemas de la vida real.
Estados jerárquicamente anidados
La innovación más importante de las máquinas de estados UML con respecto a las máquinas de estados finitos tradicionales es la introducción de estados jerárquicamente anidados (por eso los diagramas de estados también se denominan máquinas de estados jerárquicas o HSM ). La semántica asociada al anidamiento de estados es la siguiente (véase la Figura 3): Si un sistema se encuentra en el estado anidado, por ejemplo, "resultado" (denominado subestado ), también se encuentra (implícitamente) en el estado circundante "encendido" (denominado superestado ). Esta máquina de estados intentará gestionar cualquier evento en el contexto del subestado, que conceptualmente se encuentra en el nivel inferior de la jerarquía. Sin embargo, si el subestado "resultado" no especifica cómo gestionar el evento, este no se descarta silenciosamente como en una máquina de estados "plana" tradicional; sino que se gestiona automáticamente en el contexto de nivel superior del superestado "encendido". Esto es lo que significa que el sistema se encuentre en el estado "resultado" y también en el estado "encendido". Por supuesto, el anidamiento de estados no se limita a un solo nivel, y la regla simple del procesamiento de eventos se aplica recursivamente a cualquier nivel de anidamiento.

Los estados que contienen otros estados se denominan estados compuestos ; por el contrario, los estados sin estructura interna se denominan estados simples . Un estado anidado se denomina subestado directo cuando no está contenido por ningún otro estado; de lo contrario, se denomina subestado anidado transitivamente .
Debido a que la estructura interna de un estado compuesto puede ser arbitrariamente compleja, cualquier máquina de estados jerárquica puede considerarse como la estructura interna de algún estado compuesto (de nivel superior). Es conceptualmente conveniente definir un estado compuesto como la raíz última de la jerarquía de la máquina de estados. En la especificación UML, cada máquina de estados tiene una región (la raíz abstracta de toda jerarquía de máquinas de estados) [ 9 ] , que contiene todos los demás elementos de la máquina de estados completa. La representación gráfica de esta región que lo engloba todo es opcional.
Como puede verse, la semántica de la descomposición jerárquica de estados está diseñada para facilitar la reutilización del comportamiento. Los subestados (estados anidados) solo necesitan definir las diferencias con respecto a los superestados (estados que los contienen). Un subestado puede heredar fácilmente [ 6 ] el comportamiento común de sus superestados simplemente ignorando los eventos comúnmente manejados, que luego son manejados automáticamente por estados de nivel superior. En otras palabras, el anidamiento jerárquico de estados permite la programación por diferencia . [ 10 ]
El aspecto de la jerarquía de estados que más se enfatiza es la abstracción , una técnica antigua y eficaz para gestionar la complejidad. En lugar de abordar todos los aspectos de un sistema complejo simultáneamente, a menudo es posible ignorar (abstraer) algunas partes del sistema. Los estados jerárquicos constituyen un mecanismo ideal para ocultar detalles internos, ya que el diseñador puede fácilmente ampliar o reducir la vista para ocultar o mostrar estados anidados.
Sin embargo, los estados compuestos no solo ocultan la complejidad, sino que también la reducen activamente mediante el potente mecanismo de procesamiento jerárquico de eventos. Sin esta reutilización, incluso un aumento moderado en la complejidad del sistema podría provocar un incremento desmesurado en el número de estados y transiciones. Por ejemplo, la máquina de estados jerárquica que representa la calculadora de bolsillo (Figura 3) evita repetir las transiciones Borrar y Apagar en prácticamente todos los estados. Evitar la repetición permite que el crecimiento de las máquinas de estados jerárquicas se mantenga proporcional al crecimiento de la complejidad del sistema. A medida que el sistema modelado crece, también aumenta la oportunidad de reutilización, lo que potencialmente contrarresta el aumento desproporcionado en el número de estados y transiciones típico de las máquinas de estados finitos tradicionales.
Regiones ortogonales
El análisis mediante descomposición jerárquica de estados puede incluir la aplicación de la operación «OR exclusivo» a cualquier estado dado. Por ejemplo, si un sistema se encuentra en el superestado «on» (Figura 3), puede darse el caso de que también se encuentre en el subestado «operand1», en el subestado «operand2», en el subestado «opEntered» o en el subestado «result». Esto llevaría a describir el superestado «on» como un «estado OR».
Los diagramas de estados UML también introducen la descomposición AND complementaria. Dicha descomposición significa que un estado compuesto puede contener dos o más regiones ortogonales (ortogonal significa compatible e independiente en este contexto) y que estar en dicho estado compuesto implica estar en todas sus regiones ortogonales simultáneamente. [ 11 ]
Las regiones ortogonales abordan el problema frecuente del aumento combinatorio en el número de estados cuando el comportamiento de un sistema se fragmenta en partes independientes y concurrentes. Por ejemplo, además del teclado principal, un teclado de computadora tiene un teclado numérico independiente. De la discusión anterior, recordemos los dos estados del teclado principal ya identificados: "predeterminado" y "Bloq Mayús" (véase la Figura 1). El teclado numérico también puede estar en dos estados —"números" y "flechas"— dependiendo de si Bloq Num está activo. El espacio de estados completo del teclado en la descomposición estándar es, por lo tanto, el producto cartesiano de los dos componentes (teclado principal y teclado numérico) y consta de cuatro estados: "predeterminado-números", "predeterminado-flechas", "Bloq Mayús-números" y "Bloq Mayús-flechas". Sin embargo, esta sería una representación poco natural porque el comportamiento del teclado numérico no depende del estado del teclado principal y viceversa. El uso de regiones ortogonales permite evitar la mezcla de comportamientos independientes como un producto cartesiano y, en cambio, que permanezcan separados, como se muestra en la Figura 4.

Nótese que si las regiones ortogonales son totalmente independientes entre sí, su complejidad combinada es simplemente aditiva, lo que significa que el número de estados independientes necesarios para modelar el sistema es simplemente la suma k + l + m + ... , donde k, l, m, ... denotan el número de estados OR en cada región ortogonal. Sin embargo, el caso general de dependencia mutua, por otro lado, resulta en una complejidad multiplicativa, por lo que, en general, el número de estados necesarios es el producto k × l × m × ... .
En la mayoría de las situaciones reales, las regiones ortogonales serían solo aproximadamente ortogonales (es decir, no verdaderamente independientes). Por lo tanto, los diagramas de estados UML ofrecen diversas maneras para que las regiones ortogonales se comuniquen y sincronicen sus comportamientos. Entre estos amplios conjuntos de mecanismos (a veces complejos), quizás la característica más importante sea que las regiones ortogonales pueden coordinar sus comportamientos enviándose instancias de eventos entre sí.
Aunque las regiones ortogonales implican independencia de ejecución (lo que permite mayor o menor concurrencia), la especificación UML no exige que se asigne un hilo de ejecución independiente a cada región ortogonal (si bien esto puede hacerse si se desea). De hecho, lo más común es que las regiones ortogonales se ejecuten dentro del mismo hilo. [ 12 ] La especificación UML solo exige que el diseñador no dependa de ningún orden particular para que las instancias de eventos se envíen a las regiones ortogonales correspondientes.
Acciones de entrada y salida
En un diagrama de estados UML, cada estado puede tener acciones de entrada opcionales , que se ejecutan al entrar en un estado, así como acciones de salida opcionales , que se ejecutan al salir de un estado. Las acciones de entrada y salida están asociadas a los estados, no a las transiciones. Independientemente de cómo se entre o se salga de un estado, todas sus acciones de entrada y salida se ejecutarán. Debido a esta característica, los diagramas de estados se comportan como máquinas de Moore . La notación UML para las acciones de entrada y salida de un estado consiste en colocar la palabra reservada "entrada" (o "salida") en el estado justo debajo del compartimento del nombre, seguida de la barra inclinada y la lista de acciones arbitrarias (véase la Figura 5).

El valor de las acciones de entrada y salida radica en que proporcionan medios para la inicialización y limpieza garantizadas , de forma muy similar a los constructores y destructores de clases en la programación orientada a objetos . Por ejemplo, consideremos el estado "door_open" de la Figura 5, que corresponde al comportamiento del horno tostador cuando la puerta está abierta. Este estado tiene un requisito crítico de seguridad muy importante: siempre debe desactivarse el calentador cuando la puerta esté abierta. Además, mientras la puerta esté abierta, la luz interna que ilumina el horno debe encenderse.
Por supuesto, este comportamiento podría modelarse añadiendo acciones apropiadas (desactivar el calentador y encender la luz) a cada ruta de transición que conduzca al estado "puerta_abierta" (el usuario puede abrir la puerta en cualquier momento durante el horneado o el tostado, o incluso cuando el horno no se esté utilizando). No hay que olvidar apagar la luz interior con cada transición que salga del estado "puerta_abierta". Sin embargo, esta solución provocaría la repetición de acciones en muchas transiciones. Y lo que es más importante, este enfoque deja el diseño expuesto a errores durante las modificaciones posteriores del comportamiento (por ejemplo, el siguiente programador que trabaje en una nueva función, como el dorado superior, podría olvidarse de desactivar el calentador al pasar al estado "puerta_abierta").
Las acciones de entrada y salida permiten implementar el comportamiento deseado de forma más segura, sencilla e intuitiva. Como se muestra en la Figura 5, se puede especificar que la acción de salida de "calentar" desactive el calentador, la acción de entrada a "abrir puerta" encienda la luz del horno y la acción de salida de "abrir puerta" la apague. El uso de acciones de entrada y salida es preferible a colocar una acción en una transición, ya que evita la codificación repetitiva y mejora la funcionalidad al eliminar un riesgo de seguridad (calentador encendido con la puerta abierta). La semántica de las acciones de salida garantiza que, independientemente de la ruta de transición, el calentador se desactivará cuando la tostadora no esté en el estado de "calentar".
Dado que las acciones de entrada se ejecutan automáticamente al acceder a un estado asociado, suelen determinar las condiciones de funcionamiento o la identidad del estado, de forma similar a como un constructor de clase determina la identidad del objeto que se está creando. Por ejemplo, la identidad del estado "calefacción" se determina por el hecho de que el calentador esté encendido. Esta condición debe establecerse antes de acceder a cualquier subestado de "calefacción", ya que las acciones de entrada a un subestado de "calefacción", como "tostado", dependen de la correcta inicialización del superestado "calefacción" y solo realizan las modificaciones derivadas de dicha inicialización. Por consiguiente, el orden de ejecución de las acciones de entrada siempre debe ir del estado más externo al más interno (de arriba hacia abajo).
Como era de esperar, este orden es análogo al orden en que se invocan los constructores de clase. La construcción de una clase siempre comienza en la raíz de la jerarquía de clases y continúa a través de todos los niveles de herencia hasta la clase que se instancia. La ejecución de las acciones de salida, que corresponde a la invocación del destructor, se realiza en el orden inverso (de abajo hacia arriba).
transiciones internas
Es muy común que un evento solo provoque la ejecución de algunas acciones internas, pero no un cambio de estado (transición de estado). En este caso, todas las acciones ejecutadas conforman la transición interna . Por ejemplo, al teclear en un teclado, este genera diferentes códigos de caracteres. Sin embargo, a menos que se pulse la tecla Bloq Mayús, el estado del teclado no cambia (no se produce ninguna transición de estado). En UML, esta situación debe modelarse con transiciones internas, como se muestra en la Figura 6. La notación UML para las transiciones internas sigue la sintaxis general utilizada para las acciones de salida (o entrada), con la diferencia de que, en lugar de la palabra entrada (o salida), la transición interna se etiqueta con el evento desencadenante (por ejemplo, véase la transición interna desencadenada por el evento ANY_KEY en la Figura 6).

En ausencia de acciones de entrada y salida, las transiciones internas serían idénticas a las autotransiciones (transiciones en las que el estado de destino es el mismo que el estado de origen). De hecho, en una máquina de Mealy clásica , las acciones se asocian exclusivamente con transiciones de estado, por lo que la única forma de ejecutar acciones sin cambiar el estado es mediante una autotransición (representada como un bucle dirigido en la Figura 1 del inicio de este artículo). Sin embargo, en presencia de acciones de entrada y salida, como en los diagramas de estados UML, una autotransición implica la ejecución de acciones de entrada y salida y, por lo tanto, es claramente diferente de una transición interna.
A diferencia de una autotransición, no se ejecutan acciones de entrada ni de salida como resultado de una transición interna, incluso si esta se hereda de un nivel superior de la jerarquía que el estado activo actual. Las transiciones internas heredadas de superestados en cualquier nivel de anidamiento actúan como si se hubieran definido directamente en el estado activo actual.
Secuencia de ejecución de transición
El anidamiento de estados, combinado con las acciones de entrada y salida, complica significativamente la semántica de transición de estados en las HSM en comparación con las FSM tradicionales. Al tratar con estados anidados jerárquicamente y regiones ortogonales , el simple término " estado actual" puede resultar bastante confuso. En una HSM, puede haber más de un estado activo simultáneamente. Si la máquina de estados se encuentra en un estado hoja que está contenido en un estado compuesto (que posiblemente esté contenido en un estado compuesto de nivel superior, y así sucesivamente), todos los estados compuestos que contienen directa o transitivamente el estado hoja también están activos. Además, debido a que algunos de los estados compuestos en esta jerarquía pueden tener regiones ortogonales, el estado activo actual se representa en realidad mediante un árbol de estados que comienza con la única región en la raíz y desciende hasta los estados simples individuales en las hojas. La especificación UML se refiere a dicho árbol de estados como configuración de estado. [ 1 ]

En UML, una transición de estado puede conectar directamente dos estados cualesquiera. Estos dos estados, que pueden ser compuestos, se designan como el origen principal y el destino principal de una transición. La Figura 7 muestra un ejemplo sencillo de transición y explica las funciones de los estados en dicha transición. La especificación UML prescribe que tomar una transición de estado implica ejecutar las acciones en la siguiente secuencia predefinida (véase la Sección 14.2.3.9.6 del Lenguaje Unificado de Modelado de OMG (OMG UML) [ 1 ] ):
- Evalúe la condición de guarda asociada a la transición y realice los siguientes pasos solo si la condición de guarda se evalúa como VERDADERA.
- Salir de la configuración del estado de origen.
- Ejecutar las acciones asociadas a la transición.
- Introduzca la configuración del estado objetivo.
La secuencia de transición es fácil de interpretar en el caso simple de que tanto la fuente principal como el destino principal estén anidados al mismo nivel. Por ejemplo, la transición T1 que se muestra en la Figura 7 provoca la evaluación de la condición g(); seguida de la secuencia de acciones: a(); b(); t(); c(); d();y e(); suponiendo que la condición g()se evalúa como VERDADERA.
Sin embargo, en el caso general de estados de origen y destino anidados en diferentes niveles de la jerarquía de estados, puede que no sea inmediatamente obvio cuántos niveles de anidamiento se deben abandonar. La especificación UML [ 1 ] prescribe que una transición implica salir de todos los estados anidados desde el estado activo actual (que puede ser un subestado directo o transitivo del estado principal de origen) hasta, pero sin incluir, el estado del ancestro común más bajo (ACL) de los estados principales de origen y destino. Como su nombre indica, el ACL es el estado compuesto más bajo que es simultáneamente un superestado (ancestro) tanto del estado de origen como del estado de destino. Como se describió anteriormente, el orden de ejecución de las acciones de salida siempre es desde el estado más profundamente anidado (el estado activo actual) hacia arriba en la jerarquía hasta el ACL, pero sin abandonar el ACL. Por ejemplo, el ACL(s1,s2) de los estados "s1" y "s2" que se muestra en la Figura 7 es el estado "s".
El acceso a la configuración del estado objetivo comienza desde el nivel donde se detuvieron las acciones de salida (es decir, desde dentro del LCA). Como se describió anteriormente, las acciones de entrada deben ejecutarse desde el estado de nivel más alto, descendiendo por la jerarquía de estados hasta el estado objetivo principal. Si el estado objetivo principal es compuesto, la semántica UML prescribe "explorar" su submáquina de forma recursiva utilizando las transiciones iniciales locales. El acceso a la configuración del estado objetivo se completa solo después de encontrar un estado hoja que no tenga transiciones iniciales.
Transiciones locales versus externas
Antes de UML 2, [ 1 ] la única semántica de transición en uso era la transición externa , en la que siempre se sale del origen principal de la transición y siempre se entra en el destino principal de la transición. UML 2 conservó la semántica de "transición externa" para compatibilidad con versiones anteriores, pero también introdujo un nuevo tipo de transición llamada transición local (véase la Sección 14.2.3.4.4 del Lenguaje Unificado de Modelado (UML) [ 1 ] ). Para muchas topologías de transición, las transiciones externas y locales son en realidad idénticas. Sin embargo, una transición local no provoca la salida y la reentrada al estado de origen principal si el estado de destino principal es un subestado del origen principal. Además, una transición de estado local no provoca la salida y la reentrada al estado de destino principal si el destino principal es un superestado del estado de origen principal.

La figura 8 compara las transiciones locales (a) y externas (b). En la fila superior, se observa el caso de la fuente principal que contiene el destino principal. La transición local no provoca la salida de la fuente, mientras que la transición externa provoca la salida y la reentrada a la fuente. En la fila inferior de la figura 8, se observa el caso del destino principal que contiene la fuente principal. La transición local no provoca la entrada al destino, mientras que la transición externa provoca la salida y la reentrada al destino.
Aplazamiento del evento
En ocasiones, un evento se produce en un momento especialmente inoportuno, cuando la máquina de estados se encuentra en un estado que le impide gestionarlo. En muchos casos, la naturaleza del evento permite posponerlo (dentro de ciertos límites) hasta que el sistema alcance otro estado en el que esté mejor preparado para gestionar el evento original.
Las máquinas de estados UML proporcionan un mecanismo especial para diferir eventos en los estados. En cada estado, se puede incluir una cláusula [event list]/defer. Si ocurre un evento en la lista de eventos diferidos del estado actual, el evento se guardará (diferirá) para su procesamiento posterior hasta que se ingrese a un estado que no incluya el evento en su lista de eventos diferidos. Al ingresar a dicho estado, la máquina de estados UML recuperará automáticamente cualquier evento guardado que ya no esté diferido y luego consumirá o descartará estos eventos. Es posible que un superestado tenga una transición definida en un evento que es diferido por un subestado. De acuerdo con otras áreas en la especificación de las máquinas de estados UML, el subestado tiene precedencia sobre el superestado, el evento se diferirá y la transición para el superestado no se ejecutará. En el caso de regiones ortogonales donde una región ortogonal diferencie un evento y otra lo consuma, el consumidor tiene precedencia y el evento se consume y no se diferencie.
Las limitaciones de las máquinas de estados UML
Los diagramas de estados de Harel, precursores de las máquinas de estados UML, se concibieron como un formalismo visual para sistemas complejos [ 2 ] , por lo que desde su creación se han asociado inseparablemente con la representación gráfica en forma de diagramas de estados. Sin embargo, es importante comprender que el concepto de máquina de estados UML trasciende cualquier notación particular, gráfica o textual. La especificación UML [ 1 ] deja clara esta distinción al separar claramente la semántica de la máquina de estados de la notación.
Sin embargo, la notación de los diagramas de estados UML no es puramente visual. Cualquier máquina de estados no trivial requiere una gran cantidad de información textual (por ejemplo, la especificación de acciones y condiciones). La sintaxis exacta de las expresiones de acción y condición no está definida en la especificación UML, por lo que muchas personas usan inglés estructurado o, más formalmente, expresiones en un lenguaje de implementación como C , C++ o Java . [ 13 ] En la práctica, esto significa que la notación de diagramas de estados UML depende en gran medida del lenguaje de programación específico .
Sin embargo, la mayoría de las semánticas de diagramas de estados están fuertemente sesgadas hacia la notación gráfica. Por ejemplo, los diagramas de estados representan de forma deficiente la secuencia de procesamiento, ya sea el orden de evaluación de las condiciones de guarda o el orden de envío de eventos a regiones ortogonales . La especificación UML evita estos problemas al exigir al diseñador que no se base en ninguna secuencia en particular. Sin embargo, cuando las máquinas de estados UML se implementan en la práctica, inevitablemente existe un control total sobre el orden de ejecución, lo que genera críticas de que la semántica UML puede ser innecesariamente restrictiva. De manera similar, los diagramas de estados requieren una gran cantidad de elementos auxiliares (pseudoestados, como uniones, bifurcaciones, puntos de decisión, etc.) para representar gráficamente el flujo de control. En otras palabras, estos elementos de la notación gráfica no aportan mucho valor a la hora de representar el flujo de control en comparación con el código estructurado simple .
La notación y la semántica UML están orientadas principalmente a las herramientas UML computarizadas . Una máquina de estados UML, tal como se representa en una herramienta, no es solo el diagrama de estados, sino una combinación de representación gráfica y textual que captura con precisión tanto la topología de estados como las acciones. Los usuarios de la herramienta pueden obtener varias vistas complementarias de la misma máquina de estados, tanto visuales como textuales, mientras que el código generado es solo una de las muchas vistas disponibles.
Referencias
- 1 2 3 4 5 6 7 8 9 10 11 "Máquinas de estados". Lenguaje Unificado de Modelado 2.5.1 . Número de documento OMG formal/2017-12-05. Organización de Desarrollo de Estándares del Grupo de Gestión de Objetos (OMG SDO). Diciembre de 2017. pág. 305.
- 1 2 Harel, David (1987). "Statecharts: Un formalismo visual para sistemas complejos" (PDF) .
- ↑ D. Drusinsky, Modelado y verificación mediante diagramas de estados UML , Elsevier , 2006
- ↑ Samek, Miro (marzo de 2009). "Un curso intensivo sobre máquinas de estados UML" .
- ↑ Samek, Miro (2008). Diagramas de estados UML prácticos en C/C++, Segunda edición: Programación orientada a eventos para sistemas embebidos . Newnes. pág. 728. ISBN 978-0-7506-8706-5.
- 1 2 Samek, Miro (abril de 2003). "¿Quién movió mi estado?" . C/C++ Users Journal, columna The Embedded Angle.
- ↑ Selic, Bran; Gullekson, Garth; Ward, Paul T. (1994). Modelado orientado a objetos en tiempo real . John Wiley & Sons. pág. 525. ISBN 0-471-59917-4.
- ↑ Samek, Miro (agosto de 2003). "Volver a lo básico" . C/C++ Users Journal, columna The Embedded Angle.
- ↑ "Región". Lenguaje Unificado de Modelado 2.5.1 . Número de documento OMG formal/2017-12-05. Organización de Desarrollo de Estándares del Grupo de Gestión de Objetos (OMG SDO). Diciembre de 2017. pág. 352.
- ↑ Samek, Miro (junio de 2003). "Dj Vu" . C/C++ Users Journal, columna The Embedded Angle. Archivado del original el 30 de septiembre de 2012.
- ↑ Harel, David; Politi, Michal (1998). Modelado de sistemas reactivos con diagramas de estados: el enfoque STATEMATE . McGraw-Hill. pág. 258. ISBN 0-07-026205-5.
- ↑ Douglass, Bruce Powel (1999). Doing Hard Time: Developing Real-Time Systems with UML, Objects, Frameworks, and Patterns . Addison Wesley. p. 749. ISBN 0-201-49837-5.
- ↑ Douglass, Bruce Powel (enero de 1999). "Diagramas de estados UML" . Programación de sistemas embebidos.
Enlaces externos
- Lenguaje Unificado de Modelado 2.5.1 . Número de documento OMG formal/2017-12-05. Organización de Desarrollo de Estándares del Grupo de Gestión de Objetos (OMG SDO). Diciembre de 2017.
- Diagramas de máquina de estados UML 2
- Autómatas (computación)
- Modelos de computación
- electrónica digital
- Métodos formales
- Diagramas del Lenguaje Unificado de Modelado