Lasst uns einen unendlich langen Aufsatz schreiben! Aber das ist eine unmögliche Aufgabe. Wir können jedoch einen Prozess erstellen, der, wenn er ewig läuft, einen unendlich langen Aufsatz erzeugen würde. Nah genug .
Jetzt können Sie natürlich einen langen und sich wiederholenden Aufsatz mit einer einzigen Zeile Python-Code erstellen:
>>> "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. '
Gähn … Buh! Stattdessen werden wir in diesem Artikel einen viel interessanteren Aufsatz erstellen, der das State-Entwurfsmuster verwendet.
Zunächst werden wir verstehen, was Zustandsmaschinen sind und wie sie mit dem Zustandsentwurfsmuster zusammenhängen. Als nächstes erstellen wir eine Zustandsmaschine, die einen (einigermaßen) interessanten und unendlichen Aufsatz generieren kann. Anschließend gehen wir kurz auf das staatliche Entwurfsmuster ein. Abschließend werden wir diese Zustandsmaschine mithilfe des Zustandsentwurfsmusters in objektorientierten Code übersetzen.
Software-Entwurfsmuster sind wirksame Methoden zur Lösung häufig auftretender Probleme. Bei richtiger Anwendung können Softwareentwurfsmuster wie das Zustandsmuster Ihnen dabei helfen, besser skalierbare, wartbare und testbare Software zu schreiben.
Im Wesentlichen übersetzt das State-Entwurfsmuster eine State Machine in objektorientierten Code.
Wenn Sie mit Zustandsautomaten nicht vertraut sind, handelt es sich um ein ziemlich einfaches Konzept. Eine Zustandsmaschine verfügt über Zustände und Übergänge . Zustände sind bestimmte Eigenschaften unseres interessierenden Systems, und Zustandsübergänge sind Aktionen, die diese Eigenschaften ändern und dadurch auch eine Zustandsänderung bewirken.
Da ich (unter anderem) einen Robotik-Hintergrund habe und Zustandsmaschinen in der Robotik häufig verwendet werden, werde ich ein einfaches Beispiel eines Roboterstaubsaugers verwenden, um zu veranschaulichen, wie Zustandsmaschinen funktionieren.
Das Zustandsmaschinendiagramm vermittelt ein intuitives Bild der Funktionsweise des Roboters, auch wenn Sie noch nie mit Zustandsmaschinen in Berührung gekommen sind. Lassen Sie uns diesen Vorgang Schritt für Schritt durchgehen.
Somit hat unser Roboterstaubsauger drei Zustände – Angedockt , Reinigen und Laden – und verfügt über Übergänge, die auf der sensorischen Erkennung des Bodens und seines Akkus basieren.
Nachdem wir nun Zustandsautomaten auf einer grundlegenden Ebene verstanden haben, erstellen wir einen Zustandsautomaten, der in der Lage ist, einen unendlichen Aufsatz zu schreiben.
Oben sehen Sie eine Zustandsmaschine, die englische Grammatik verwendet, um einen Aufsatz zu generieren, der aus kurzen, einfachen Sätzen besteht. Ich verspreche, dass wir sehr bald zu einer interessanteren Version kommen werden, aber dies sollte als guter Ausgangspunkt für das Verständnis dienen. Sehen wir uns an, wie es funktioniert.
Diese Zustandsmaschine könnte einen (unsinnigen) Aufsatz erzeugen, der so aussieht.
Die Welt bellt rot! Cousin Harry regnet Foul? Die Tiger schimmern lustig. …
Obwohl „nicht deterministisch“ kompliziert klingt, bedeutet es für unsere Zwecke lediglich, etwas Zufälligkeit hinzuzufügen. Im Wesentlichen führen wir eine Art Münzwurf ein, bevor wir zu einigen Bundesstaaten wechseln. Du wirst sehen, was ich meine.
Der obige nichtdeterministische Zustandsautomat ist dem vorherigen sehr ähnlich. Die einzigen Unterschiede sind:
Mit der Einführung von Zufälligkeit, Negation und Konjunktionen können wir nun interessantere Sätze mit variabler Länge generieren.
Lassen Sie uns nun verstehen, wie das staatliche Entwurfsmuster funktioniert. Denken Sie auch hier daran, dass wir versuchen, eine Zustandsmaschine in objektorientierten Code zu übersetzen.
Beachten Sie in der Zustandsmaschine zur Aufsatzgenerierung, dass jeder Staat zwei Dinge tun muss.
Aus der Sicht eines bestimmten Staates gibt es nichts anderes , was er wissen oder tun muss. Anstatt uns auf die Komplexität des gesamten Systems – all seiner Zustände und Übergänge – einzulassen, können wir uns immer nur auf einen Zustand nach dem anderen konzentrieren. Meiner Ansicht nach ist diese Art der Isolation und Entkopplung das größte Verkaufsargument des Staatsmusters.
Unten haben wir ein UML- Diagramm für das State-Entwurfsmuster. Es gibt einen bestimmten Kontext, in dem jeder der Zustände operiert, veranschaulicht durch die Context
Klasse. Das Kontextobjekt verfügt über ein privates Statusattribut, mit dem es den aktuellen Status aufruft, um seine Aktion auszuführen. Jeder Zustand implementiert eine State
mit Methoden zum Ausführen seiner Aktion oder Operation und zum Zurückgeben des nächsten Zustands.
Wenn wir dies auf das Beispiel zur Aufsatzgenerierung übertragen, sieht das UML-Diagramm so aus.
WordState
ist jetzt eine abstrakte Klasse (durch Kursivschrift gekennzeichnet) anstelle einer Schnittstelle. Abstrakte Klassen können einige abstrakte (nicht implementierte) Methoden und Attribute haben, während andere definiert werden können. Schnittstellen sind völlig abstrakt: Alle ihre Methoden sind abstrakt. Ich habe diese Änderung vorgenommen, weil die Implementierung generateWord
in allen Bundesstaaten gleich ist und es sinnvoll ist, doppelten Code zu vermeiden.
Lassen Sie uns die einzelnen Attribute und Methoden oben aufschlüsseln. In der EssayContext
Klasse haben wir:
state
: Referenz auf das aktuelle WordState
Objekt.essayBody
: Liste aller bisher generierten Wörter.setState()
: Setter zum Ändern des state
.addWord()
: Methode zum Hinzufügen des nächsten Wortes zum Aufsatztext.generateEssay()
: Wir rufen diese Methode auf, um unseren Aufsatz zu generieren; Wir hören auf, wenn die Länge des essayBody
größer als length
ist.printEssay()
: Gibt eine Zeichenfolge des generierten Aufsatzes zurück.
In der abstrakten Klasse WordState
haben wir:
wordList
: Abstrakte Eigenschaft (kursiv dargestellt) für eine Liste von Wörtern, aus der wir Wörter zum Generieren auswählen.generateWord()
: Methode, die generierte Wörter zum Aufsatzkontext hinzufügt.nextState()
: Abstrakte Methode zur Rückgabe des nächsten Zustands.
Wir verwenden NounState
als repräsentatives Beispiel für alle anderen konkreten Zustände, die von WordState
geerbt werden.
wordList
: Eine Liste von Substantiven, aus denen wir Wörter zur Generierung auswählen.nextState()
: Gibt den nächsten Zustand zurück.
Jetzt haben wir alles, was wir brauchen, um dies tatsächlich im Code zu implementieren. Lasst uns weitermachen und genau das tun!
Schreiben wir zunächst die EssayContext
Klasse in eine Datei namens essay_context.py
. Wir lassen den Kamelfall hinter uns und wechseln zum Schlangenfall, denn Python ist ja eine... Schlange (tut mir leid).
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)
Anschließend fügen wir die Zustände in einer Datei namens word_state.py
hinzu.
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
Zum Schluss fügen wir Code hinzu, um alles in main.py
auszuführen.
from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay())
Wenn wir python main.py
ausführen, erhalten wir die folgende Ausgabe (aufgrund des Nichtdeterminismus jedes Mal unterschiedlich):
'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 !'
Nicht schlecht für ein so einfaches System! Wir können auch die verschiedenen Wortlisten erweitern oder weitere Zustände hinzufügen, um die Aufsatzerstellung anspruchsvoller zu gestalten. Wir könnten sogar einige LLM-APIs einführen, um unsere Aufsätze auf die nächste Stufe zu heben.
Zustandsmaschinen und das Zustandsmuster eignen sich hervorragend zum Modellieren und Erstellen von Systemen mit einer klar definierten Vorstellung von einem „Zustand“. Das heißt, mit jedem Zustand sind spezifische Verhaltensweisen und Eigenschaften verbunden. Der Saugroboter reinigt, ist angedockt oder lädt. Ihr Fernseher kann ein- oder ausgeschaltet sein und die Fernbedienungstasten des Fernsehers verhalten sich je nach Zustand des Fernsehers unterschiedlich.
Es eignet sich auch gut zum Generieren oder Identifizieren von Sequenzen mit einem genau definierten Muster. Dies gilt für unser Beispiel zur Aufsatzerstellung.
Abschließend fragen Sie sich vielleicht: „Was hat das alles für einen Sinn?“ Warum haben wir uns die Mühe gemacht, die verschiedenen Zustände und Klassen zu definieren, um diesen „unendlichen“ Aufsatz zu erstellen? Wir hätten 20 (oder weniger) Zeilen Python-Code schreiben können, um das gleiche Verhalten zu erreichen.
Die kurze Antwort lautet: bessere Skalierbarkeit .
Stellen Sie sich vor, wir hätten statt nur drei oder fünf Staaten 50 oder 500 Staaten. Das ist keine Übertreibung; Echte Unternehmenssysteme weisen diesen Grad an Komplexität auf. Plötzlich scheint das Staatsmuster aufgrund seiner Entkopplung und Isolation viel attraktiver zu sein. Wir können uns jeweils nur auf einen Zustand konzentrieren, ohne das gesamte System im Kopf behalten zu müssen. Es ist einfacher, Änderungen einzuführen, da ein Status keine Auswirkungen auf andere hat. Es ermöglicht auch einfachere Unit-Tests, ein großer Teil eines skalierbaren und wartbaren Systems.
Letztlich geht es beim Staatsmuster nicht nur um die Verwaltung von Staaten; Wie alle Entwurfsmuster ist es eine Blaupause für den Aufbau von Systemen, die ebenso skalierbar und wartbar wie anspruchsvoll sind.