¡Escribamos un ensayo infinitamente largo ! Pero esa es una tarea imposible. Sin embargo, podemos crear un proceso que, si se ejecutara eternamente, generaría un ensayo infinitamente largo. Suficientemente cerca .
Ahora, obviamente puedes generar un ensayo largo y repetitivo con una sola línea de código Python:
>>> "This is water. " * 20 'This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. '
Bostezo … ¡abucheo! En cambio, generaremos un ensayo mucho más interesante utilizando el patrón de diseño Estado en este artículo.
Primero, entenderemos qué son las máquinas de estados y cómo se relacionan con el patrón de diseño de estados. A continuación, crearemos una máquina de estados que pueda generar un ensayo infinito (razonablemente) interesante. Luego, repasaremos brevemente el patrón de diseño estatal. Finalmente, traduciremos esa máquina de estados a código orientado a objetos utilizando el patrón de diseño de estados.
Los patrones de diseño de software son formas efectivas de resolver problemas que ocurren comúnmente. Cuando se aplican adecuadamente, los patrones de diseño de software, como el patrón de estado, pueden ayudarlo a escribir un software mejor escalable, mantenible y comprobable.
En esencia, el patrón de diseño State traduce una máquina de estados en código orientado a objetos.
Si no está familiarizado con las máquinas de estados, es un concepto bastante simple. Una máquina de estados tiene estados y transiciones . Los estados son ciertas propiedades de nuestro sistema de interés, y las transiciones de estado son acciones que cambian estas propiedades y, por lo tanto, también causan un cambio de estado.
Como tengo experiencia en robótica (entre otras cosas) y debido a que las máquinas de estados se usan ampliamente en robótica, usaré un ejemplo simple de un robot aspirador para ilustrar cómo funcionan las máquinas de estados.
El diagrama de la máquina de estados muestra una imagen intuitiva de cómo opera el robot, incluso si nunca se ha encontrado con máquinas de estados. Repasemos esta operación paso a paso.
Así, nuestro robot aspirador tiene tres estados ( Atracado , Limpiando y Cargando ) y tiene transiciones basadas en la detección sensorial del suelo y su batería.
Ahora que entendemos las máquinas de estados en un nivel básico, creemos una máquina de estados capaz de escribir un ensayo infinito.
Arriba hay una máquina de estados que utiliza la gramática inglesa para generar un ensayo compuesto por oraciones cortas y simples. Prometo que llegaremos a una versión más interesante muy pronto, pero esto debería servir como un buen punto de partida para la comprensión. Repasemos cómo funciona.
Esta máquina de estados podría generar un ensayo (sin sentido) similar a este.
¡El mundo ladra rojo! ¿El primo Harry llueve mal? Los tigres brillan de forma divertida. …
Aunque “no determinista” suena complicado, para nuestros propósitos simplemente significa agregar algo de aleatoriedad. Básicamente, estamos agregando una especie de lanzamiento de moneda antes de hacer la transición a algunos de los estados. Verás lo que quiero decir.
La máquina de estados no determinista anterior es muy similar a la anterior. Las únicas diferencias son:
Con la introducción de la aleatoriedad, la negación y las conjunciones, ahora podemos generar oraciones más interesantes y de longitud variable.
Ahora, comprendamos cómo funciona el patrón de diseño estatal. Nuevamente, recuerde que estamos intentando traducir una máquina de estados a código orientado a objetos.
En la máquina de estados de generación de ensayos, observe que cada estado necesita hacer dos cosas.
Desde el punto de vista de un Estado en particular, no hay nada más que deba saber o hacer. En lugar de quedar estancados por la complejidad de todo el sistema (todos sus estados y transiciones), podemos centrarnos simplemente en un estado a la vez. En mi opinión, este tipo de aislamiento y desacoplamiento es el mayor atractivo del modelo estatal.
A continuación, tenemos un diagrama UML para el patrón de diseño State. Hay algún contexto en el que opera cada uno de los estados, ilustrado por la clase Context
. El objeto de contexto tiene un atributo de estado privado, que utiliza para llamar al estado actual para realizar su acción. Cada estado implementa una interfaz State
con métodos para realizar su acción u operación y devolver el siguiente estado.
Si asignamos esto al ejemplo de generación de ensayos, el diagrama UML se verá así.
WordState
es ahora una clase abstracta (indicada en cursiva) en lugar de una interfaz. Las clases abstractas pueden tener algunos métodos y atributos abstractos (no implementados), mientras que otros pueden definirse. Las interfaces son totalmente abstractas: todos sus métodos son abstractos. Hice este cambio porque la implementación generateWord
es la misma en todos los estados y es bueno evitar el código duplicado.
Analicemos cada uno de los atributos y métodos anteriores. En la clase EssayContext
, tenemos:
state
: Referencia al objeto WordState
actual.essayBody
: Lista de todas las palabras generadas hasta el momento.setState()
: Configurador para cambiar el atributo state
.addWord()
: método para agregar la siguiente palabra al cuerpo del ensayo.generateEssay()
: llamamos a este método para generar nuestro ensayo; Nos detenemos cuando el essayBody
tiene una longitud mayor que length
.printEssay()
: Devuelve una cadena del ensayo generado.
En la clase abstracta WordState
, tenemos:
wordList
: Propiedad abstracta (indicada en cursiva) para una lista de palabras entre las cuales elegimos las palabras para generar.generateWord()
: método que agrega la palabra generada al contexto del ensayo.nextState()
: método abstracto para devolver el siguiente estado.
Usaremos NounState
como ejemplo representativo de todos los demás estados concretos heredados de WordState
.
wordList
: una lista de sustantivos de la cual elegimos palabras para generar.nextState()
: Devuelve el siguiente estado.
Ahora tenemos todo lo que necesitamos para implementar esto en el código. ¡Sigamos adelante y hagamos justamente eso!
Primero escribamos la clase EssayContext
en un archivo llamado essay_context.py
. Nos desharemos del caso del camello y cambiaremos al caso de la serpiente porque, bueno, Python es una... serpiente (lo siento).
from word_state import WordState class EssayContext: def __init__(self, state: WordState): self.state = state self.essay_body: list[str] = [] def set_state(self, state: WordState): self.state = state def add_word(self, word: str): self.essay_body.append(word) def generate_essay(self, length: int): while len(self.essay_body) < length: self.state.generate_word(self) def print_essay(self) -> str: return " ".join(self.essay_body)
Luego, agreguemos los estados en un archivo llamado word_state.py
.
import abc import numpy as np class WordState(abc.ABC): word_list: list[str] @classmethod def generate_word(cls, context: "EssayContext"): word = np.random.choice(cls.word_list) context.add_word(word) context.set_state(cls.next_state()) @classmethod @abc.abstractmethod def next_state(cls) -> "WordState": pass class NounState(WordState): word_list: list[str] = ["everything", "nothing"] @classmethod def next_state(cls): return VerbState class VerbState(WordState): word_list: list[str] = ["is", "was", "will be"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return NegationState return AdjectiveState class NegationState(WordState): word_list: list[str] = ["not"] @classmethod def next_state(cls): return AdjectiveState class AdjectiveState(WordState): word_list: list[str] = ["fantastic", "terrible"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return ConjunctionState return EndmarkState class ConjunctionState(WordState): word_list: list[str] = ["and", "but"] @classmethod def next_state(cls): return NounState class EndmarkState(WordState): word_list: list[str] = [".", "!"] @classmethod def next_state(cls): return NounState
Finalmente, agreguemos código para ejecutar todo en main.py
from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay())
La ejecución python main.py
nos da el siguiente resultado (diferente cada vez debido al no determinismo):
'everything is not terrible and nothing was terrible ! everything will be not fantastic but everything is fantastic . everything will be fantastic . nothing will be fantastic and nothing will be terrible ! everything was not fantastic and everything will be not terrible . everything was terrible . nothing was terrible but nothing will be fantastic ! nothing is not terrible . nothing was not fantastic but everything was not fantastic ! everything will be not fantastic but everything will be terrible ! everything will be not fantastic . everything is fantastic but nothing will be not terrible ! everything will be not fantastic but nothing was not fantastic !'
¡Nada mal para un sistema tan simple! También podemos ampliar las distintas listas de palabras o agregar más estados para hacer que la generación del ensayo sea más sofisticada. Incluso podríamos presentar algunas API de LLM para llevar nuestros ensayos al siguiente nivel.
Las máquinas de estados y el patrón de estado son ideales para modelar y crear sistemas con una noción bien definida de "estado". Es decir, existen comportamientos y propiedades específicas asociadas a cada estado. El robot aspirador está limpiando, acoplado o cargándose. Su televisor puede estar encendido o apagado, y los botones del control remoto del televisor actuarán de manera diferente según el estado del televisor.
También es una buena opción para generar o identificar secuencias con un patrón bien definido. Esto se aplica a nuestro ejemplo de generación de ensayos.
Finalmente, podrías preguntarte: “¿Cuál es el sentido de todo esto?” ¿Por qué nos tomamos la molestia de definir los distintos estados y clases para generar este ensayo “infinito”? Podríamos haber escrito 20 (o menos) líneas de código Python para lograr el mismo comportamiento.
La respuesta corta es para una mejor escalabilidad .
Imagínese si, en lugar de sólo tres o cinco estados, tuviéramos 50 o 500 estados. Esto no es una hipérbole; Los sistemas empresariales reales tienen ese nivel de complejidad. De repente, el patrón del Estado parece mucho más atractivo debido a su desacoplamiento y aislamiento. Podemos concentrarnos en un estado a la vez sin tener que mantener todo el sistema en nuestras cabezas. Es más fácil introducir cambios ya que un estado no afectará a los demás. También permite realizar pruebas unitarias más sencillas, una gran parte de un sistema escalable y mantenible.
En última instancia, el patrón estatal no se trata sólo de gestionar estados; Como todos los patrones de diseño, es un modelo para construir sistemas que sean tan escalables y mantenibles como sofisticados.