stringtranslate.com

máquina de estados UML

La máquina de estados UML , [1] anteriormente conocida como diagrama de estados UML , es una extensión del concepto matemático de autómata finito en aplicaciones de informática tal como se expresa en la notación del Lenguaje de modelado unificado (UML).

Los conceptos detrás de esto tratan de organizar la forma en que funciona un dispositivo, programa de computadora u otro proceso (a menudo técnico) de modo que una entidad o cada una de sus subentidades esté siempre exactamente en uno de varios estados posibles y donde haya buenos estados. -transiciones condicionales 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 ampliada 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 manteniendo sus principales beneficios. Los diagramas de estado UML introducen los nuevos conceptos de estados jerárquicamente anidados y regiones ortogonales, al tiempo que amplían la noción de acciones. Las máquinas de estados UML tienen las características tanto de las máquinas Mealy como de las máquinas Moore . Apoyan 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 con 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 de máquinas de estados: máquinas de estados de comportamiento y máquinas de estados de protocolo . Las máquinas de estados de comportamiento se pueden utilizar 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 estado de protocolo se utilizan para expresar protocolos de uso y se pueden utilizar para especificar los escenarios de uso legales de clasificadores, interfaces y puertos.

Conceptos básicos de la máquina de estados

Muchos sistemas de software están controlados por eventos , lo que significa que esperan continuamente que ocurra algún evento externo o interno , como un clic del mouse, la presión de un botón, un tic de tiempo o la llegada de un paquete de datos. Después de reconocer el evento, dichos sistemas reaccionan realizando el cálculo apropiado que puede incluir la manipulación del hardware o la generación de eventos "soft" que activan otros componentes de software internos. (Es por eso que los sistemas controlados por eventos también se denominan sistemas reactivos ). Una vez que se completa el manejo del evento, el sistema vuelve a esperar el siguiente evento.

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 estados entre esos estados se puede abstraer y representar como una máquina de estados finitos (FSM).

El concepto de FSM es importante en la programación basada en 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 la cantidad de rutas de ejecución a través del código, simplificar las condiciones probadas en cada punto de bifurcación y simplificar el cambio entre diferentes modos de ejecución. [5] Por el contrario, el uso de programación basada en 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 estado UML básicos

UML conserva la forma general de los diagramas de estado tradicionales . Los diagramas de estado UML son gráficos dirigidos en los que los nodos denotan estados y los conectores denotan transiciones de estado. Por ejemplo, la Figura 1 muestra un diagrama de estado UML correspondiente a la máquina de estado del teclado de la computadora. En UML, los estados se representan como rectángulos redondeados etiquetados con nombres de estados. Las transiciones, representadas como flechas, están etiquetadas con los eventos desencadenantes seguidos opcionalmente de 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 comienza por primera vez. Cada diagrama de estado debería tener dicha transición, que no debería estar etiquetada, ya que no es desencadenada por un evento. La transición inicial puede tener acciones asociadas.

Figura 1: Diagrama de estado UML que representa la máquina de estado del teclado de la computadora

Eventos

Un evento es algo que sucede que afecta al sistema. Estrictamente hablando, en la especificación UML, [1] el término evento se refiere al tipo de ocurrencia más que a cualquier instancia concreta de esa 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 el Encendido, pero encenderlo mañana a las 10:05:36 será solo una instancia del evento Encendido.

Un evento puede tener parámetros asociados , lo que permite que la instancia del evento transmita no sólo la ocurrencia de algún incidente interesante sino también información cuantitativa sobre esa ocurrencia. Por ejemplo, el evento Keystroke generado al presionar una tecla en el teclado de una 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 lo generó y puede transmitir este suceso a una o más máquinas de estado. Una vez generada, la instancia del evento pasa por un ciclo de vida de procesamiento que puede constar de hasta tres etapas. Primero, la instancia del evento se recibe cuando se acepta y espera su procesamiento (por ejemplo, se coloca en la cola de eventos ). Posteriormente, la instancia del 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 estados termina de procesar la instancia del evento. Una instancia de evento consumida ya no está disponible para su procesamiento.

Estados

Cada máquina de estados tiene un estado que gobierna la reacción de la máquina de estados ante los eventos. Por ejemplo, cuando presiona una tecla en un teclado, el código de carácter generado será un carácter en mayúscula o en minúscula, dependiendo de si el bloqueo de mayúsculas está activo. Por lo tanto, el comportamiento del teclado se puede dividir en dos estados: el estado "predeterminado" y el estado "mayúsculas_bloqueadas". (La mayoría de los teclados incluyen un LED que indica que el teclado está en el estado "caps_locked".) El comportamiento de un teclado depende sólo de ciertos aspectos de su historial, es decir, si se ha presionado la tecla Bloq Mayús, pero no, por ejemplo, de cuántas y exactamente qué otras teclas se han pulsado anteriormente. 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 estado de software (y especialmente de los FSM clásicos), el término estado a menudo se entiende como una única variable de estado que puede asumir sólo un número limitado de valores determinados a priori (por ejemplo, dos valores en el caso del teclado, o más). generalmente, algún tipo de variable con un tipo de enumeración en muchos lenguajes de programación). La idea de la variable de estado (y el modelo FSM clásico) es que el valor de la variable de estado define completamente el estado actual del sistema en un momento dado. El concepto de estado reduce el problema de identificar el contexto de ejecución en el código a probar solo la variable de estado en lugar de muchas variables, eliminando así mucha lógica condicional.

estados extendidos

En la práctica, sin embargo, interpretar todo el estado de la máquina de estados como una única variable de estado rápidamente se vuelve poco práctico para todas las máquinas de estados más allá de las más simples. De hecho, incluso si tuviéramos 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 y conduciría 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 comúnmente se divide 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 de todo el estado. En esta interpretación, un cambio de variable no siempre implica un cambio de los aspectos cualitativos del comportamiento del sistema y, por 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 extendidos pueden aplicar el formalismo subyacente a problemas mucho más complejos de lo que es práctico sin incluir variables de estado extendido. Por ejemplo, si tenemos que implementar algún tipo de límite en nuestro FSM (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 reduce con cada pulsación de tecla sin cambiar la variable de estado .

Figura 2: Máquina de estado extendida del "teclado barato" con variable de estado extendida key_count y varias condiciones de protección

El diagrama de estado de la Figura 2 es un ejemplo de una máquina de estado extendida, en la que la condición completa del sistema (llamada 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 obvia de las máquinas de estados extendidos es la flexibilidad. Por ejemplo, cambiar el límite regido por key_count1000 a 10000 pulsaciones de teclas no complicaría en absoluto la máquina de estados extendida. La única modificación requerida sería cambiar el valor de inicialización de la key_countvariable de estado extendida durante la inicialización.

Sin embargo, esta flexibilidad de las máquinas de estados extendidos tiene un precio debido al complejo acoplamiento entre los aspectos "cualitativos" y "cuantitativos" del estado extendido. El acoplamiento se produce a través de las condiciones de protección adjuntas a las transiciones, como se muestra en la Figura 2.

Condiciones de guardia

Las condiciones de guardia (o simplemente guardias) son expresiones booleanas evaluadas dinámicamente en función del valor de las variables de estado extendidas y los parámetros de eventos. Las condiciones de protección afectan el comportamiento de una máquina de estados al habilitar acciones o transiciones solo cuando se evalúan como VERDADERO y las deshabilitan cuando se evalúan como FALSO. En la notación UML, las condiciones de protección se muestran entre corchetes (por ejemplo, [key_count == 0]en la Figura 2).

La necesidad de guardias es la consecuencia inmediata de agregar variables de estado de memoria extendida al formalismo de la máquina de estados. Si se usan con moderación, las variables de estado extendidas y las protecciones constituyen un poderoso mecanismo que puede simplificar los diseños. Por otro lado, es posible abusar de los estados extendidos y de las guardias con bastante facilidad. [8]

Acciones y transiciones

Cuando se envía una instancia de evento, la máquina de estado responde realizando acciones , como cambiar una variable, realizar E/S, invocar una función, generar otra instancia de evento o cambiar a otro estado. Cualquier valor de parámetro asociado con el evento actual está disponible para todas las acciones causadas directamente por ese evento.

El cambio de un estado a otro se llama transición de estado , y el evento que lo provoca se llama evento desencadenante, o simplemente desencadenante . En el ejemplo del teclado, si el teclado está en el estado "predeterminado" cuando se presiona la tecla Bloq Mayús, el teclado ingresará al estado "bloqueado en mayúsculas". Sin embargo, si el teclado ya está en el estado "bloqueado en mayúsculas", presionar Bloq Mayús provocará una transición diferente, del estado "bloqueado en mayúsculas" al estado "predeterminado". En ambos casos, presionar Bloq Mayús es el evento desencadenante.

En máquinas de estados extendidos, una transición puede tener una protección, lo que significa que la transición puede "dispararse" sólo si la protección se evalúa como VERDADERA. Un estado puede tener muchas transiciones en respuesta al mismo desencadenante, siempre que tengan guardias que no se superpongan; sin embargo, esta situación podría crear problemas en la secuencia de evaluación de las guardias cuando ocurre el desencadenante común. La especificación UML [1] intencionalmente no estipula ningún orden en particular; más bien, UML impone al diseñador la carga de diseñar protecciones de tal manera que el orden de su evaluación no importe. En la práctica, esto significa que las expresiones de guardia no deberían tener efectos secundarios, al menos ninguno que altere la evaluación de otros guardias que tienen el mismo desencadenante.

Modelo de ejecución hasta su finalización

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 que pueda comenzar a procesar el siguiente evento. Este modelo de ejecución se llama ejecución hasta el final o RTC.

En el modelo RTC, el sistema procesa 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 estado vuelva a estar inactiva. Esta semántica evita por completo cualquier problema de concurrencia interna dentro de una única máquina de estado. El modelo RTC también soluciona el problema conceptual del procesamiento de acciones asociadas con transiciones, donde la máquina de estados no está 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 no tiene importancia práctica.

Sin embargo, tenga en cuenta que RTC no significa que una máquina de estados tenga que monopolizar la CPU hasta que se complete el paso RTC. [1] La restricción de preferencia solo se aplica al contexto de tarea de la máquina de estado que ya está ocupada procesando eventos. En un entorno multitarea , se pueden ejecutar otras tareas (no relacionadas con el contexto de tarea de la máquina de estado ocupada), posiblemente adelantándose a la máquina de estado que se está ejecutando actualmente. Mientras otras máquinas de estado no compartan variables u otros recursos entre sí, no existen riesgos de concurrencia .

La ventaja clave del procesamiento RTC es la simplicidad. Su mayor desventaja es que la capacidad de respuesta de una máquina de estados está determinada por su paso RTC más largo. Lograr pasos cortos de RTC a menudo puede complicar significativamente los diseños en tiempo real.

Extensiones UML al formalismo FSM tradicional

Aunque los FSM tradicionales son una herramienta excelente para abordar problemas más pequeños, también se sabe en general que tienden a volverse inmanejables, incluso para sistemas moderadamente involucrados. Debido al fenómeno conocido como explosión de estados y transiciones , la complejidad de un FSM tradicional tiende a crecer mucho más rápido que la complejidad del sistema que describe. Esto sucede porque el formalismo tradicional de la máquina estatal provoca repeticiones. Por ejemplo, si intenta representar el comportamiento de una simple calculadora de bolsillo con un FSM tradicional, inmediatamente notará que muchos eventos (por ejemplo, presionar el botón Borrar o Apagar) se manejan de manera idéntica en muchos estados. Un MEV convencional que se muestra en la figura siguiente no tiene medios para capturar esos puntos en común 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 con el fin de compartirlo entre muchos estados.

Una calculadora de bolsillo (izquierda) y la máquina de estados tradicional con múltiples transiciones Borrar y Apagar (derecha)

Las máquinas de estados UML abordan exactamente esta deficiencia de los FSM convencionales. Proporcionan una serie de características para eliminar las repeticiones de modo que la complejidad de una máquina de estados UML ya no explote sino que tienda a representar fielmente la complejidad del sistema reactivo que describe. Obviamente, estas características son muy interesantes para los desarrolladores de software, porque sólo ellas hacen que el enfoque de la máquina de estados completo sea realmente aplicable a problemas de la vida real.

Estados anidados jerárquicamente

La innovación más importante de las máquinas de estados UML sobre las FSM tradicionales es la introducción de estados jerárquicamente anidados (es por eso que los diagramas de estado también se denominan máquinas de estados jerárquicos o HSM ). La semántica asociada con el anidamiento de estados es la siguiente (ver Figura 3): Si un sistema está en el estado anidado, por ejemplo "resultado" (llamado subestado ) , también (implícitamente) está en el estado circundante "encendido" (llamado el superestado ). Esta máquina de estados intentará manejar cualquier evento en el contexto del subestado, que conceptualmente se encuentra en el nivel inferior de la jerarquía. Sin embargo, si el "resultado" del subestado no prescribe cómo manejar el evento, el evento no se descarta silenciosamente como en una máquina de estados "plana" tradicional; más bien, se maneja automáticamente en el contexto de nivel superior del superestado "encendido". Esto es lo que significa que el sistema esté tanto en estado "resultado" como en "encendido". Por supuesto, el anidamiento de estados no se limita a un solo nivel, y la simple regla del procesamiento de eventos se aplica recursivamente a cualquier nivel de anidamiento.

Figura 3: Una calculadora de bolsillo (izquierda) y la máquina de estados UML con anidamiento de estados (derecha)

Los estados que contienen otros estados se llaman 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 le 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 verse como una 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 cada jerarquía de máquinas de estados),

[9]


que contiene todos los demás elementos de toda la máquina de estados. La representación gráfica de esta región que lo incluye todo es opcional.

Como puede ver, la semántica de la descomposición del estado jerárquico está diseñada para facilitar la reutilización del comportamiento. Los subestados (estados anidados) sólo necesitan definir las diferencias con los superestados (estados contenedores). Un subestado puede heredar fácilmente [6] el comportamiento común de su(s) superestado(s) simplemente ignorando los eventos comúnmente manejados, que luego son manejados automáticamente por estados de nivel superior. En otras palabras, el anidamiento de estados jerárquicos permite la programación por diferencia . [10]

El aspecto de la jerarquía estatal que se destaca con más frecuencia es la abstracción , una técnica antigua y poderosa para afrontar la complejidad. En lugar de abordar todos los aspectos de un sistema complejo al mismo tiempo, a menudo es posible ignorar (abstraer) algunas partes del sistema. Los estados jerárquicos son un mecanismo ideal para ocultar detalles internos porque el diseñador puede alejar o acercar fácilmente para ocultar o mostrar estados anidados.

Sin embargo, los estados compuestos no se limitan a ocultar la complejidad; también lo reducen activamente a través del poderoso mecanismo del procesamiento jerárquico de eventos. Sin esa reutilización, incluso un aumento moderado en la complejidad del sistema podría conducir a un aumento explosivo 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 los HSM siga siendo proporcional al crecimiento de la complejidad del sistema. A medida que crece el sistema modelado, la oportunidad de reutilización también aumenta y, por lo tanto, contrarresta potencialmente el aumento desproporcionado en el número de estados y transiciones típicos de los FSM tradicionales.

Regiones ortogonales

El análisis por 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 está en el superestado "on" (Figura 3), puede darse el caso de que también esté en el subestado "operand1" O en el subestado "operand2" O en el subestado "opEntered" O en el "resultado" subestado. Esto llevaría a la descripción del superestado "on" como un "estado OR".

Los diagramas de estado UML también introducen la descomposición AND complementaria. Tal 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 tal estado compuesto implica estar en todas sus regiones ortogonales simultáneamente. [11]

Las regiones ortogonales abordan el problema frecuente de un aumento combinatorio en el número de estados cuando el comportamiento de un sistema se fragmenta en partes independientes y simultáneamente activas. Por ejemplo, además del teclado principal, el teclado de un ordenador tiene un teclado numérico independiente. De la discusión anterior, recuerde los dos estados del teclado principal ya identificados: "predeterminado" y "mayúsculas bloqueadas" (consulte 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 estado 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: "números predeterminados", "flechas predeterminados", "números bloqueados en mayúsculas", " y "mayúsculas_bloqueadas–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, permanecer separados, como se muestra en la Figura 4.

Figura 4: Dos regiones ortogonales (teclado principal y teclado numérico) de un teclado de computadora

Tenga en cuenta 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 números de estados OR en cada región ortogonal. Sin embargo, el caso general de dependencia mutua, por otro lado, resulta en complejidad multiplicativa, por lo que, en general, el número de estados necesarios es el producto k × l × m × ....

En la mayoría de situaciones de la vida real, las regiones ortogonales serían sólo aproximadamente ortogonales (es decir, no verdaderamente independientes). Por lo tanto, los diagramas de estado UML proporcionan varias formas para que las regiones ortogonales se comuniquen y sincronicen sus comportamientos. Entre estos ricos conjuntos de mecanismos (a veces complejos), quizás la característica más importante es que las regiones ortogonales pueden coordinar sus comportamientos enviándose instancias de eventos entre sí.

Aunque las regiones ortogonales implican independencia de ejecución (permitiendo más o menos concurrencia), la especificación UML no requiere que se asigne un hilo de ejecución separado a cada región ortogonal (aunque esto se puede hacer si se desea). De hecho, lo más habitual es que las regiones ortogonales se ejecuten dentro del mismo hilo. [12] La especificación UML sólo requiere que el diseñador no dependa de ningún orden particular para que las instancias de eventos se envíen a las regiones ortogonales relevantes.

Acciones de entrada y salida.

Cada estado en un diagrama de estado UML puede tener acciones de entrada opcionales , que se ejecutan al entrar a 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 con estados, no con transiciones. Independientemente de cómo se entra o sale de un estado, se ejecutarán todas sus acciones de entrada y salida. Debido a esta característica, los gráficos de estados se comportan como máquinas de Moore . La notación UML para acciones de entrada y salida de estado consiste en colocar la palabra reservada "entrada" (o "salida") en el estado justo debajo del compartimento del nombre, seguida de la barra diagonal y la lista de acciones arbitrarias (consulte la Figura 5).

Figura 5: Máquina de estado del horno tostador con acciones de entrada y salida

El valor de las acciones de entrada y salida es que proporcionan medios para una inicialización y limpieza garantizadas , muy parecidos a los constructores y destructores de clases en la programación orientada a objetos . Por ejemplo, considere el estado "puerta_abierta" de la Figura 5, que corresponde al comportamiento del horno tostador mientras la puerta está abierta. Este estado tiene un requisito crítico de seguridad muy importante: desactive siempre el calentador cuando la puerta esté abierta. Además, mientras la puerta está abierta, la lámpara interna que ilumina el horno debe encenderse.

Por supuesto, tal comportamiento podría modelarse agregando 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 "hornear" o "tostar"). " o cuando el horno no se utiliza en absoluto). No se debe olvidar apagar la lámpara interna cada vez que se sale del estado "puerta_abierta". Sin embargo, tal solución provocaría la repetición de acciones en muchas transiciones. Más importante aún, este enfoque deja el diseño propenso a errores durante modificaciones posteriores del comportamiento (por ejemplo, el próximo programador que trabaje en una nueva característica, como el dorado superior, podría simplemente olvidarse de desactivar el calentador en la transición a "puerta_abierta").

Las acciones de entrada y salida permiten implementar el comportamiento deseado de una manera más segura, sencilla e intuitiva. Como se muestra en la Figura 5, se podría especificar que la acción de salida de "calefacción" desactiva el calentador, la acción de entrada a "puerta_abierta" enciende la lámpara del horno y la acción de salida de "puerta_abierta" apaga la lámpara. El uso de acciones de entrada y salida es preferible a colocar una acción en una transición porque evita la codificación repetitiva y mejora la función al eliminar un peligro para la seguridad; (calefactor encendido mientras la puerta está 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 "calentamiento".

Debido a que las acciones de entrada se ejecutan automáticamente cada vez que se ingresa a un estado asociado, a menudo determinan las condiciones de operación o la identidad del estado, de manera muy similar a como un constructor de clase determina la identidad del objeto que se está construyendo. Por ejemplo, la identidad del estado de "calentamiento" está determinada por el hecho de que el calentador está encendido. Esta condición debe establecerse antes de ingresar a cualquier subestado de "calentamiento" porque las acciones de entrada a un subestado de "calentamiento", como "tostar", dependen de la inicialización adecuada del superestado de "calentamiento" y realizan solo las diferencias con respecto a esta inicialización. En consecuencia, el orden de ejecución de las acciones de entrada siempre debe proceder del estado más externo al estado más interno (de arriba hacia abajo).

No sorprende que este orden sea análogo al orden en que se invocan los constructores de clases. La construcción de una clase siempre comienza en la raíz misma de la jerarquía de clases y sigue a través de todos los niveles de herencia hasta la clase de la que se crea una instancia. La ejecución de las acciones de salida, que corresponden a la invocación del destructor, se realiza exactamente en el orden inverso (de abajo hacia arriba).

Transiciones internas

Muy comúnmente, un evento provoca que solo se ejecuten algunas acciones internas pero no conduce a un cambio de estado (transición de estado). En este caso, todas las acciones ejecutadas comprenden la transición interna . Por ejemplo, cuando uno escribe en un teclado, este responde generando diferentes códigos de caracteres. Sin embargo, a menos que se presione 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 transiciones internas sigue la sintaxis general utilizada para las acciones de salida (o entrada), excepto que en lugar de la palabra entrada (o salida) se utiliza la transición interna. está etiquetado con el evento desencadenante (por ejemplo, consulte la transición interna desencadenada por el evento ANY_KEY en la Figura 6).

Figura 6: Diagrama de estado UML de la máquina de estado del teclado con transiciones internas

En ausencia de acciones de entrada y salida, las transiciones internas serían idénticas a las autotransiciones (transiciones en las que el estado objetivo es el mismo que el estado fuente). De hecho, en una máquina Mealy clásica , las acciones están asociadas exclusivamente con transiciones de estado, por lo que la única forma de ejecutar acciones sin cambiar de estado es a través de una autotransición (representada como un bucle dirigido en la Figura 1 desde la parte superior de este artículo). Sin embargo, en presencia de acciones de entrada y salida, como en los diagramas de estado UML, una autotransición implica la ejecución de acciones de entrada y salida y, por lo tanto, es distintivamente diferente de una transición interna.

A diferencia de una autotransición, nunca se ejecutan acciones de entrada o salida como resultado de una transición interna, incluso si la transición interna se hereda de un nivel superior de la jerarquía que el estado actualmente activo. Las transiciones internas heredadas de superestados en cualquier nivel de anidamiento actúan como si estuvieran definidas directamente en el estado actualmente activo.

Secuencia de ejecución de transición

El anidamiento de estados combinado con acciones de entrada y salida complica significativamente la semántica de transición de estado en los HSM en comparación con los FSM tradicionales. Cuando se trata de estados jerárquicamente anidados y regiones ortogonales, el término simple estado actual puede resultar bastante confuso. En un HSM, más de un estado puede estar activo a la vez. Si la máquina de estados está en un estado hoja que está contenido en un estado compuesto (que posiblemente esté contenido en un estado compuesto de nivel superior, etc.), 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 en realidad está representado por un árbol de estados que comienza con una sola región en la raíz hasta estados simples individuales en las hojas. La especificación UML se refiere a dicho árbol de estado como configuración de estado. [1]

Figura 7: Roles estatales en una transición estatal

En UML, una transición de estado puede conectar directamente dos estados cualesquiera. Estos dos estados, que pueden ser compuestos, son designados como la fuente principal y el objetivo principal de una transición. La Figura 7 muestra un ejemplo de transición simple y explica los roles del estado en esa transición. La especificación UML prescribe que realizar una transición de estado implica ejecutar las acciones en la siguiente secuencia predefinida (consulte la Sección 14.2.3.9.6 del Lenguaje de modelado unificado OMG (OMG UML) [1] ):

  1. Evalúe la condición de protección asociada con la transición y realice los siguientes pasos solo si la protección se evalúa como VERDADERA.
  2. Salga de la configuración del estado de origen.
  3. Ejecutar las acciones asociadas a la transición.
  4. Ingrese la configuración del estado objetivo.

La secuencia de transición es fácil de interpretar en el caso simple de que tanto el origen principal como el destino principal se anidan en el mismo nivel. Por ejemplo, la transición T1 mostrada en la Figura 7 provoca la evaluación de la guardia g(); seguido de la secuencia de acciones: a(); b(); t(); c(); d();y e(); asumiendo que la guardia g()evalúa como VERDADERO.

Sin embargo, en el caso general de estados de origen y de destino anidados en diferentes niveles de la jerarquía de estados, puede que no sea inmediatamente obvio de cuántos niveles de anidación es necesario salir. La especificación UML [1] prescribe que una transición implica salir de todos los estados anidados desde el estado activo actual (que podría ser un subestado directo o transitivo del estado de origen principal) hasta el estado de ancestro menos común (LCA) , pero sin incluirlo. de los principales estados de origen y de destino. Como su nombre lo indica, el LCA es el estado compuesto más bajo que es simultáneamente un superestado (ancestro) tanto del estado fuente como del estado objetivo. Como se describió anteriormente, el orden de ejecución de las acciones de salida es siempre desde el estado más profundamente anidado (el estado activo actual) hacia arriba en la jerarquía hasta el LCA, pero sin salir del LCA. Por ejemplo, el LCA(s1,s2) de los estados "s1" y "s2" mostrados en la Figura 7 es el estado "s".

El ingreso a la configuración del estado objetivo comienza desde el nivel donde terminaron las acciones de salida (es decir, desde el interior del LCA). Como se describió anteriormente, las acciones de entrada deben ejecutarse comenzando desde el estado de nivel más alto hacia abajo en la jerarquía de estados hasta el estado objetivo principal. Si el estado objetivo principal es compuesto, la semántica de UML prescribe "perforar" su submáquina de forma recursiva utilizando las transiciones iniciales locales. La configuración del estado objetivo se ingresa por completo solo después de encontrar un estado hoja que no tiene 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 de la fuente principal de la transición y siempre se ingresa al 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 (consulte la Sección 14.2.3.4.4 del Lenguaje de modelado unificado (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 el reingreso 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 el reingreso al estado objetivo principal si el objetivo principal es un superestado del estado fuente principal.

Figura 8: Transiciones locales (a) versus externas (b).

La Figura 8 contrasta las transiciones locales (a) y externas (b). En la fila superior, ve 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 el reingreso a la fuente. En la fila inferior de la Figura 8, se ve el caso del objetivo principal que contiene la fuente principal. La transición local no provoca la entrada al objetivo, mientras que la transición externa provoca la salida y el reingreso al objetivo.

Aplazamiento del evento

A veces, un evento llega en un momento particularmente inconveniente, cuando una máquina de estado está en un estado que no puede manejar el evento. En muchos casos, la naturaleza del evento es tal que puede posponerse (dentro de límites) hasta que el sistema entre en otro estado, en el que esté mejor preparado para manejar el evento original.

Las máquinas de estados UML proporcionan un mecanismo especial para aplazar eventos en los estados. En cada estado, puedes 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 futuro hasta que se ingrese a un estado que no incluya el evento en su lista de eventos diferidos. Al entrar en dicho estado, la máquina de estado UML recuperará automáticamente cualquier evento guardado que ya no esté aplazado y luego consumirá o descartará estos eventos. Es posible que un superestado tenga una transición definida en un evento diferido por un subestado. De manera consistente con otras áreas en la especificación de las máquinas de estados UML, el subestado tiene prioridad 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 difiere un evento y otra consume el evento, el consumidor tiene prioridad y el evento se consume y no se difiere.

Las limitaciones de las máquinas de estados UML

Los diagramas de estado de Harel, que son los precursores de las máquinas de estado UML, se inventaron como "un formalismo visual para sistemas complejos", [2] por lo que desde sus inicios han estado inseparablemente asociados con la representación gráfica en forma de diagramas de estado. Sin embargo, es importante entender que el concepto de máquina de estados UML trasciende cualquier notación particular, gráfica o textual. La especificación UML [1] hace evidente 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 estado 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 guardias). La sintaxis exacta de las expresiones de acción y protección no está definida en la especificación UML, por lo que mucha gente usa 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 del diagrama de estado UML depende en gran medida del lenguaje de programación específico .

Sin embargo, la mayoría de la semántica de los gráficos de estados está fuertemente sesgada hacia la notación gráfica. Por ejemplo, los diagramas de estado representan mal la secuencia de procesamiento, ya sea el orden de evaluación de los guardias o el orden de envío de eventos a regiones ortogonales. La especificación UML evita estos problemas imponiendo al diseñador la carga de no depender de ninguna secuencia en particular. Sin embargo, cuando se implementan realmente las máquinas de estados UML, existe inevitablemente un control total sobre el orden de ejecución, lo que da lugar a críticas de que la semántica de UML puede ser innecesariamente restrictiva. De manera similar, los diagramas de estados requieren una gran cantidad de elementos de plomería (pseudoestados, como uniones, bifurcaciones, uniones, puntos de elección, etc.) para representar gráficamente el flujo de control. En otras palabras, estos elementos de la notación gráfica no añaden 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 de UML están realmente orientadas a herramientas UML computarizadas . Una máquina de estados UML, tal como se representa en una herramienta, no es sólo el diagrama de estado, sino más bien una mezcla de representación gráfica y textual que captura con precisión tanto la topología del estado 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 sólo una de las muchas vistas disponibles.

Referencias

  1. ^ abcdefghijk "Máquinas de estado". Lenguaje de modelado unificado 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. 305.
  2. ^ ab Harel, David (1987). "Statecharts: un formalismo visual para sistemas complejos" (PDF) .
  3. ^ D. Drusinsky, Modelado y verificación mediante diagramas de estado UML, Elsevier , 2006
  4. ^ Samek, Miro (marzo de 2009). "Un curso intensivo sobre máquinas de estados UML".
  5. ^ Samek, Miró (2008). Diagramas de estado UML prácticos en C/C++, segunda edición: programación basada en eventos para sistemas integrados . Newnes. pag. 728.ISBN 978-0-7506-8706-5.
  6. ^ ab Samek, Miro (abril de 2003). "¿Quién movió mi estado?". Diario de usuarios de C/C++, columna Ángulo integrado.
  7. ^ Selic, salvado; Gullekson, Garth; Ward, Paul T. (1994). Modelado orientado a objetos en tiempo real . John Wiley e hijos. pag. 525.ISBN 0-471-59917-4.
  8. ^ Samek, Miro (agosto de 2003). "Volver a lo básico". Diario de usuarios de C/C++, columna Ángulo integrado.
  9. ^ "Región". Lenguaje de modelado unificado 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. 352.
  10. ^ Samek, Miro (junio de 2003). "Dj Vu". Diario de usuarios de C/C++, columna Ángulo integrado. Archivado desde el original el 30 de septiembre de 2012.
  11. ^ Harel, David; Politi, Michal (1998). Modelado de sistemas reactivos con gráficos de estados, el enfoque STATEMATE . McGraw-Hill. pag. 258.ISBN 0-07-026205-5.
  12. ^ Douglass, Bruce Powell (1999). Haciendo tiempos difíciles: desarrollo de sistemas en tiempo real con UML, objetos, marcos y patrones. Addison Wesley. pag. 749.ISBN 0-201-49837-5.
  13. ^ Douglass, Bruce Powel (enero de 1999). "Gráficos de estado UML". Programación de Sistemas Embebidos.

enlaces externos