State Pattern-CLI: Refactoring - Zustands-Singletons mit Enums

https://bildung.social/@oerinformatik/110504024656041988

https://oer-informatik.de/state-pattern-cli2

tl/dr; (ca. 5 min Lesezeit): Am Beispiel eines Command-Line-Interfaces wird das State-Pattern beispielhaft in Java implementiert. In diesem Teil geht es um das Instanzenmanagement der einzelnen Zustandsklassen mit Hilfe von Singletons/ENUMS. (Zuletzt geändert am 11.06.2023)

  1. Aufbau des State-Patterns allgemein und grundlegende Struktur
  2. Realisierung der Zustände als Singletons (per Java-ENUMS)(dieser Artikel)
  3. Realisierung der Transitionen und deren Trigger
  4. Realisierung von Effekten und Guards von Transitionen

Das bisherige Grundgerüst

Es soll eine rudimentäre Kommandozeilen-Benutzeroberfläche für ein Programm mit Hilfe des State-Pattern erstellt werden. Im ersten Teil haben wir uns das State-Pattern selbst angeschaut und die Grundstruktur für Zustandswechsel festgelegt. Bei einem Zustandswechsel haben wir im letzten Codebeispiel einfach eine neue Instanz erstellt (myUI.setState(new ListUI());). Das muss natürlich optimiert werden. Wir fügen jetzt ein Instanzenmanagement für das Projekt an. Um das große Ganze nich aus dem Blick zu verlieren werfen wir zunächst wieder einen Blick auf die Navigation, die wir modelliert hatten:

Zustandsdiagramm der Kommendozeilen-Benutzerführung
Zustandsdiagramm der Kommendozeilen-Benutzerführung

Die einzelnen Zustände hatten wir bereits über das State-Pattern modelliert, bislang jedoch nur die beiden Zustandsklassen ListUI und ‘AnmeldenUI’ implementiert. Das Verhalten ist bisher noch komplett in der vorgeschalteten abstrakten Klasse AbstractUIState realisiert. Unser Grundgerüst als Klassendiagramm sieht folgendermaßen aus:

Die vorgeschaltete abstrakte Klasse AbstractUIState
Die vorgeschaltete abstrakte Klasse AbstractUIState

Zur Orientierung nochmal im Schnelldurchlauf die Aufgaben der bisherigen Klassen:

  • Die Kontext-Klasse UIContext ist Basis des Objekts, dessen Zustand sich ändern soll.

  • Die Zustandsklassen (beispielhaft AnmeldenUI und ListUI) bestimmen später das Verhalten von UIContextin bestimmten Zuständen.

  • Das Interface UIStateInterface setzt die lose Kopplung der Zustandsklassen mit dem Kontext um.

  • Die abstrakte Klasse AbstractUIState stellt die gemeinsame Implementierungen aller Zustandsklassen bereit.

Denkbar wäre auch, das Interface komplett durch die abstrakte Klasse zu ersetzen. Wir haben diese beiden Aufgaben hier aber getrennt.

Zustände als Singletons / Variante: in ENUM-Klasse kapseln (Klasse UIStateEnum)

In diesem Schritt wollen wir dem Projekt keinerlei neue Funktionalität zuweisen, sondern die Struktur verbessern, also ein reines Refactoring durchführen: Von jeder Zustandsklasse wird lediglich ein einziges Objekt benötigt, es würde sich also anbieten, diese Klassen als Singleton zu implementieren - also dafür zu sorgen, dass immer auf die gleiche Instanz der Klasse zugegriffen wird.

Eine praktische Variante stellt die Nutzung eines Aufzählungsobjekts (ENUM) dar. Eine Aufzählung ist in Java ein Singleton und kann die Instanzen der Zustandsklassen kapseln. Die Zustandsklassen selbst müssen dann nicht als Singleton realisiert sein.

Wir erstellen eine ENUM-Klasse, von dieser Klasse Instanziieren wir für jeden Zustand eine Instanz: ANMELDEN_UI, LIST_UI usw.

Das ENUM UIStateEnum mit den Elementen ANMELDEN_UI, LIST_UI usw.
Das ENUM UIStateEnum mit den Elementen ANMELDEN_UI, LIST_UI usw.

ANMELDEN_UI ist also eine Instanz von UIStateEnum. Da wir in dieser Instanz ein Objekt speichern wollen, das UIStateInterface implementiert, müssen wir dieses übergeben. Dazu nutzen wir direkt den Konstruktor. Wir notieren also in der ENUM-Liste die Konstruktor-Aufrufe mit Parametern: (z.B. ANMELDEN_UI(new AnmeldenUI())).

Jetzt müssen wir nur noch den Konstruktor implementieren UIStateEnum(...), der die entsprechenden UIStateInterface Instanzen speichert (im Attribut uiObject).

Die Basis-Implementierung von Klasse, Konstruktor und Attribut ist folgende:

Zur Nutzung der UIStateEnums benötigen wir noch Methoden, die uns die Zustandsklassenobjekte kapseln und entkapseln.

Das ist zum einen eine Methode, die bei vorliegendem Enum-Objekt die gekapselte Zustandsklasse zurückgibt. Das ist schnell implementiert, da es sich nur um einen Getter für das Attribut handelt:

… sowie eine Methode, die für ein vorliegendes Zustandsklassenobjekt das zugehörige Enum-Objekt findet. Dazu müssen wir in die Trickkiste greifen, und für jedes Objekt mithilfe des instanceof-Operators das zugehörige Enum suchen:

An der Gesamtstruktur unseres Projekt ändert sich damit einiges: Der Kontext aggregiert nicht mehr die Zustandsobjekte, sondern er aggregiert das Enum:

Im Kontext ändert sich entsprechend das Attribut state und der zugehörige Setter zu:

In der Main-Methode wird entsprechend der neue Setter aufgerufen…

… und die drei Methoden entry(), doing() und exit() des Kontexts greifen gekapselt über das Enum auf die Zustandsobjekte zu. Dazu nutzen sie den Getter, den wir in der Enum-Klasse implementiert hatten. Beispielhaft für entry():

Nächste Schritte

Bis hierhin haben wir keinerlei neue Funktionalität hinzugefügt, ein Test des Programms sollte also wieder exakt das gleiche Ergebnis hervorbringen wie vor der Enum-Anpassung.

Weiter in Teil 3


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: State Pattern-CLI: Refactoring - Zustands-Singletons mit Enums” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/state-pattern-cli2 veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-pattern/state-pattern. Stand: 11.06.2023.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: