Écrivons un essai infiniment long ! Mais c'est une tâche impossible. Cependant, nous pouvons créer un processus qui, s’il était exécuté indéfiniment, générerait un essai infiniment long. Assez proche .
Désormais, vous pouvez évidemment générer un essai long et répétitif avec une seule ligne de code 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. '
Yaawn … huée ! Au lieu de cela, nous générerons un essai beaucoup plus intéressant en utilisant le modèle de conception d'État dans cet article.
Tout d’abord, nous comprendrons ce que sont les machines à états et comment elles sont liées au modèle de conception d’état. Ensuite, nous créerons une machine à états capable de générer un essai (raisonnablement) intéressant et infini. Ensuite, nous passerons brièvement en revue le modèle de conception de l’État. Enfin, nous traduirons cette machine à états en code orienté objet en utilisant le modèle de conception d'état.
Les modèles de conception logicielle sont des moyens efficaces pour résoudre les problèmes courants. Lorsqu'ils sont appliqués de manière appropriée, les modèles de conception logicielle tels que le modèle d'état peuvent vous aider à écrire des logiciels mieux évolutifs, maintenables et testables.
Essentiellement, le modèle de conception State traduit une machine à états en code orienté objet.
Si vous n'êtes pas familier avec les machines à états, c'est un concept assez simple. Une machine à états a des états et des transitions . Les états sont certaines propriétés de notre système d'intérêt, et les transitions d'état sont des actions qui modifient ces propriétés et provoquent ainsi également un changement d'état.
Étant donné que j'ai une formation en robotique (entre autres) et que les machines à états sont largement utilisées en robotique, je vais utiliser un exemple simple de robot aspirateur pour illustrer le fonctionnement des machines à états.
Le diagramme de la machine à états donne une image intuitive du fonctionnement du robot, même si vous n'avez jamais rencontré de machines à états. Reprenons cette opération étape par étape.
Ainsi, notre robot aspirateur a trois états — Docked , Cleaning et Charging — et possède des transitions basées sur la détection sensorielle du sol et de sa batterie.
Maintenant que nous comprenons les machines à états à un niveau de base, créons une machine à états capable d'écrire un essai infini.
Ci-dessus se trouve une machine à états qui utilise la grammaire anglaise pour générer un essai composé de phrases courtes et simples. Je vous promets que nous arriverons très bientôt à une version plus intéressante, mais cela devrait constituer un bon point de départ pour comprendre. Voyons comment cela fonctionne.
Cette machine à états pourrait générer un essai (absurde) qui ressemble à ceci.
Le monde aboie rouge ! Le cousin Harry pleut mal ? Les tigres scintillent de façon amusante. …
Bien que « non déterministe » semble compliqué, pour nos besoins, cela signifie simplement ajouter un peu de hasard. Essentiellement, nous ajoutons une sorte de tirage au sort avant de passer à certains États. Vous verrez ce que je veux dire.
La machine à états non déterministe ci-dessus est très similaire à la précédente. Les seules différences sont :
Avec l’introduction du caractère aléatoire, de la négation et des conjonctions, nous pouvons désormais générer des phrases plus intéressantes et de longueur variable.
Voyons maintenant comment fonctionne le modèle de conception d'état. Encore une fois, rappelez-vous que nous essayons de traduire une machine à états en code orienté objet.
Dans la machine à états de génération d’essais, observez que chaque état doit faire deux choses.
Du point de vue d’un État particulier, il n’y a rien d’autre qu’il ait besoin de savoir ou de faire. Au lieu de nous enliser dans la complexité du système dans son ensemble – tous ses états et transitions – nous pouvons nous concentrer uniquement sur un état à la fois. À mon avis, ce type d’ isolement et de découplage constitue le principal argument de vente du modèle étatique.
Ci-dessous, nous avons un diagramme UML pour le modèle de conception State. Il existe un certain contexte dans lequel chacun des états opère, illustré par la classe Context
. L'objet contextuel possède un attribut d'état privé, qu'il utilise pour appeler l'état actuel afin d'effectuer son action. Chaque état implémente une interface State
avec des méthodes pour effectuer son action ou opération et renvoyer l'état suivant.
Si nous mappons cela sur l'exemple de génération d'essais, le diagramme UML ressemble à ceci.
WordState
est désormais une classe abstraite (indiquée en italique) au lieu d'une interface. Les classes abstraites peuvent avoir des méthodes et des attributs abstraits (non implémentés), tandis que d'autres peuvent être définis. Les interfaces sont totalement abstraites : toutes leurs méthodes sont abstraites. J'ai effectué ce changement car l'implémentation generateWord
est la même dans tous les États et il est bon d'éviter le code en double.
Décomposons chacun des attributs et méthodes ci-dessus. Dans la classe EssayContext
, nous avons :
state
: Référence à l’objet WordState
actuel.essayBody
: Liste de tous les mots générés jusqu'à présent.setState()
: Setter pour changer l'attribut state
.addWord()
: Méthode pour ajouter le mot suivant au corps de l'essai.generateEssay()
: Nous appelons cette méthode pour générer notre essai ; nous nous arrêtons lorsque l' essayBody
a une longueur supérieure à length
.printEssay()
: renvoie une chaîne de l'essai généré.
Dans la classe abstraite WordState
, nous avons :
wordList
: Propriété abstraite (indiquée en italique) pour une liste de mots à partir de laquelle nous choisissons les mots à générer.generateWord()
: Méthode qui ajoute le mot généré au contexte de l'essai.nextState()
: Méthode abstraite pour renvoyer l'état suivant.
Nous utiliserons NounState
comme exemple représentatif pour tous les autres états concrets hérités de WordState
.
wordList
: Une liste de noms à partir de laquelle nous choisissons les mots à générer.nextState()
: Renvoie l'état suivant.
Maintenant, nous avons tout ce dont nous avons besoin pour implémenter cela dans le code. Allons-y et faisons exactement cela !
Écrivons d'abord la classe EssayContext
dans un fichier appelé essay_context.py
. Nous allons abandonner le cas du chameau et passer au cas du serpent parce que, eh bien, Python est un... serpent (désolé).
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)
Ensuite, ajoutons les états dans un fichier appelé 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
Enfin, ajoutons du code pour tout exécuter dans 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())
L'exécution python main.py
nous donne le résultat suivant (différent à chaque fois en raison du non-déterminisme) :
'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 !'
Pas mal pour un système aussi simple ! Nous pouvons également étendre les différentes listes de mots ou ajouter plus d'états pour rendre la génération d'essais plus sophistiquée. Nous pourrions même introduire des API LLM pour faire passer nos essais au niveau supérieur.
Les machines à états et le modèle State conviennent parfaitement à la modélisation et à la création de systèmes avec une notion bien définie d'« état ». Autrement dit, des comportements et des propriétés spécifiques sont associés à chaque état. Le robot aspirateur est en train de nettoyer, de se connecter ou de se charger. Votre téléviseur peut être allumé ou éteint, et les boutons de la télécommande du téléviseur agiront différemment en fonction de l'état du téléviseur.
C'est également un bon choix pour générer ou identifier des séquences avec un modèle bien défini. Cela s'applique à notre exemple de génération d'essais.
Enfin, vous pourriez demander : « À quoi ça sert tout cela ? » Pourquoi avons-nous pris tant de peine à définir les différents états et classes pour générer cet essai « infini » ? Nous aurions pu écrire 20 lignes (ou moins) de code Python pour obtenir le même comportement.
La réponse courte est pour une meilleure évolutivité .
Imaginez si, au lieu de trois ou cinq États, nous avions 50 ou 500 États. Ce n’est pas une hyperbole ; les vrais systèmes d’entreprise ont ce niveau de complexité. Le modèle étatique semble soudainement beaucoup plus attrayant en raison de son découplage et de son isolement. Nous pouvons nous concentrer uniquement sur un État à la fois sans avoir à garder l’ensemble du système en tête. Il est plus facile d’introduire des changements puisqu’un état n’affectera pas les autres. Il permet également de faciliter les tests unitaires, élément important d'un système évolutif et maintenable.
En fin de compte, le modèle étatique ne consiste pas seulement à gérer les États ; comme tous les modèles de conception, il s’agit d’un modèle pour construire des systèmes aussi évolutifs et maintenables que sophistiqués.