State Pattern-CLI: Aufbau des Patterns und Navigationsstruktur
https://bildung.social/@oerinformatik/110504024656041988
https://oer-informatik.de/state-pattern-cli1
tl/dr; (ca. 20 min Lesezeit): Am Beispiel eines Programms mit Kommandozeilenmenü wird das State-Pattern beispielhaft in Java implementiert: Eine Fingerübung, um das Pattern, Transitionen und die Elemente des UML-Zustandsdiagramms zu verinnerlichen. Im ersten Teil geht es zunächst um den Aufbau des Patterns selbst und die Grundstruktur. (Zuletzt geändert am 11.06.2023)
Der Artikel ist Bestandteil einer mehrteiligen Reihe:
- Aufbau des State-Patterns allgemein und grundlegende Struktur (dieser Artikel)
- Realisierung der Zustände als Singletons (per Java-ENUMS)
- Realisierung der Transitionen und deren Trigger
- Realisierung von Effekten und Guards von Transitionen
Ausgangspunkt: Das UML-State-Diagramm
Vorneweg: wir sind uns einig: Java ist auch nicht meine erste Wahl für eine Konsolen-App. Sinn und Zweck dieses Tutorials ist nich, für einen bestehenden Anwendungsfall eine nutzbare App zu erstellen. Ziel ist vielmehr, mit einer kleinen Fingerübung einen Einblick zu gewinnen in das State-Pattern. Ausversehen nutzen wir dazu eine ganze Reihe Notationmittel der UML und tauchen ein in die OOP mit Java, Interfaces, Abstrakten Klassen, Lambda-Ausdrücken und Generics.
Wenn in einem User-Interface (egal ob CLI, GUI oder andere) unterschiedliche Menüs, Einträge oder Dialoge geöffnet sind, reagiert das System jeweils anders auf Eingaben. Es befindet sich also in unterschiedlichen Zuständen: bei einer Webapp macht es beispielsweise einen gravierenden Unterschied, ob der Zustand der App “als Nutzer angemeldet” oder “als Gast aktiv” ist. Daher lässt sich die Navigation in einem User-Interface sehr gut mit UML-Zustandsdiagrammen planen. Um das Programm einfach zu halten modellieren wir ein Kommandozeilen-Programmfragment, dass beispielhaft mit Java realisiert werden soll. Die Struktur ist bei Graphischen Benutzeroberflächen (GUI) aber genauso.
Basis soll ein rudimentäres User-Interface sein, das für eine Adresse oder eine andere x-beliebige Entity (Datenhaltungsklasse) einfache CRUD-Masken zum Erstellen (Create), Lesen (Read), Ändern (Update) und Löschen (Delete) bereitstellt. Wenn das Programm gestartet ist und ich angemeldet bin, erhalte ich eine Liste der eingetragenen Werte und von dort in die anderen Masken wechseln, die die Detailverarbeitung übernehmen sollen. Folgendes UML-Zustandsdiagrammen modelliert die geplante Navigation des User-Interfaces:

Es soll eine möglichst einfache Navigation umgesetzt werden: per Tastendruck wird ein Menüpunkt ausgewählt. Beispielsweise triggert "a"
in jedem Zustand den Abbruch. Es sind zunächst nicht allzu viele innere Aktionen modelliert, um das Beispiel klein zu halten. Wir wollen nur prototypisch die Realisierung dieser Zustandswechsel mit Java unter Zuhilfenahme des State-Patterns zeigen.
Allgemeine Formulierung des State Pattern
Das State-Pattern ist eines der Gang-of-Four-Pattern und es ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sein interner Zustand sich ändert.
Ein Beispiel: das Verhalten der Methode reagiereAufAnstupsen()
ändert sich abhängig davon, ob das Objekt im Zustand Wach
oder Schlafend
ist.
Im State Pattern realisiert wird das, in dem der Zustand des Objekts in eigene Klassen ausgelagert wird, die das Verhalten (also die Implementierung der Methoden) dann an den Zustand anpassen.

UnterhaltungsKontext
über das Interface State
in den Implementierungen von State
mit Namen Wach
und Schlafend
realisiert wirdWie sich ein Objekt der Klasse UnterhaltungsKontext
verhält, wenn die Methode reagierenAufAnstupsen()
ausgeführt wird, bestimmt das Objekt, welches derzeit mit dem Attribut zustand
referenziert wird. Im obigen Beispiel ist zustand
lose gekoppelt über das Interface State
und kann daher Instanzen von Wach
oder Schlafend
referenzieren. Sollte neben den individuellen Methoden reagierenAufAnstupsen()
noch gemeinsames Verhalten von Wach
und Schlafend
existieren, könnte man an Stelle des Interfaces auch eine abstrakte Klasse nutzen und hier gemeinsam genutzte Attribute und Methoden implementieren. In der Beschreibung der Gang of Four wird eine abstrakte Klasse genutzt.
Den Instanzwechsel habe ich oben vereinfachend realisiert, in dem wecken()
und einschalfen()
jeweils neue Objekte von Wach
und Schlafend
erstellen - hier sollten natürlich bestehende Objekte weitergenutzt werden.
Realisierung für die Kommandozeilen-Benutzeroberfläche
Entwurf des Klassendiagramms für die konkrete Implementierung
Erster Schritt: Welche Zustände benötigen wir, welche Aktivitäten müssen bereitgestellt werden und was ist unser Kontext? Mithilfe des Zustandsdiagramms lassen sich die Zustandsklassen, der Kontext und das Interface entwerfen. Wir vereinfachen die Funktionalität zunächst stark: jeder Zustand hat je eine Aktivität, die bei Eintritt in den Zustand (entry()
), während der Zustand aktiv ist (doing()
) und bei Austritt aus dem Zustand (exit()
) ausgeführt wird. Aus dem Zustandsdiagramm ergeben sich noch einige spezialisierte Aktivitäten einiger Zustandsklassen. Es entsteht folgendes UML-Klassendiagramm:

Dreh- und Angelpunkt: die Kontextklasse UIContext
und deren Zustandswechsel
Der Kontext ist das Objekt, dessen innerer Zustand das Verhalten ändert und deswegen ausgelagert wird. In unserem Fall ist das die Kommandozeilen-Benutzeroberfläche (UI).
Wir implementieren ein aggregiertes Zustandsobjekt state
mit Setter, einen Konstruktor, die drei Aktivitäten entry()
, doing()
, exit()
, deren Verhalten jeweils an den aktiven Zustand delegiert werden und eine main()
-Methode, mit der wir das Programm starten und in der wir ein Objekt unserer Benutzeroberfläche erzeugen:
public class UIContext {
private UIStateInterface state;
public UIContext() {}
public void setState(UIStateInterface state) {
this.state = state;
}
public static void main(String[] args) {
UIContext myUI = new UIContext();
}
public void entry() {state.entry();}
public void doing() {state.doing();}
public void exit() {state.exit();}
}
Die Implementierung des Interfaces ist im obigen Klassendiagramm bereits vorgegeben:
Abstrakte Klasse mit Default-Implementierungen des Interfaces (Klasse AbstractUIState
)
Zweiter Schritt: Wir machen uns das Leben ein wenig einfacher. Für den ersten Schritt ist es noch nicht erforderlich, die im Interface deklarieren Methoden (entry()
, doing()
, exit()
) individuell zu implementieren. Es bietet sich daher an, eine abstrakte Klasse mit default-Implementierungen voran zu schalten:

Zunächst werden wir noch keine Aktivitäten für entry()
, doing()
und exit()
definieren, sondern lediglich über Ausgaben prüfen, welche Zustände jeweils genutzt werden.
public abstract class AbstractUIState implements UIStateInterface {
public void entry() {
System.out.println("<<< Entering state " + this.getClass().getSimpleName());
}
public void doing() {
System.out.println("--- Being in state " + this.getClass().getSimpleName());
}
public void exit() {
System.out.println(">>> Leaving state " + this.getClass().getSimpleName());
} }
Wir nutzen dazu das Java-Reflection-Feature: Java bietet Klassen und Methoden an, die Meta-Informationen über die aktuellen Objekte, liefern - this.getClass().getSimpleName()
beispielsweise den Namen der genutzten Klasse. Wir können uns also ausgeben lassen, welche Spezialisierung der Klasse gerade aktiv ist, ohne den Code in den Spezialisierungen implementieren zu müssen.
Die spezialisierten Zustandsklassen
Die spezialisierten Zustandsklassen sollen hier nur rudimentär implementiert werden. Einen großen Teil der Logik erben Sie bereits von der AbstractUIState
-Klasse, sodass lediglich abweichende Implementierungen oder spezielle Methoden, die die Elternklasse nicht hatte, ausimplementiert werden müssen. Beispielhaft sei dies an den Zuständen AnmeldenUI
und ListUI
gezeigt, die jeweils nur eine zusätzliche Methode (login()
bzw. abmelden()
) neu implementieren.
public class AnmeldenUI extends AbstractUIState {
public void login(){
System.out.println("eingeloggt.");
} }
public class ListUI extends AbstractUIState {
public void abmelden(){
System.out.println("abgemeldet");
} }
Der erste Test
Damit wäre das State-Pattern grundsätzlich fertig implementiert und wir können ausprobieren, ob der Zustandswechsel auch zu einem Verhaltenswechsel führt. In der main()
Methode hatten wir bereits ein Objekt unseres Kontexts erstellt. Wir müssen jetzt nur per Setter-Methode den Zustand ändern und einige Kontext-Methoden aufrufen, um zu überprüfen, dass die Implementierung der jeweiligen Zustandsklassen genutzt wird.
public static void main(String[] args) {
UIContext myUI = new UIContext();
//Erstelle einen inneren Zustand des Kontexts:
myUI.setState(new AnmeldenUI());
//Überprüfen des Verhaltens:
myUI.entry();
myUI.doing();
myUI.exit();
//Ändere den inneren Zustand des Kontexts:
myUI.setState(new ListUI());
//Überprüfen des Verhaltens:
myUI.entry();
myUI.doing();
}
Wie erwartet delegiert das Kontext-Objekt die Abarbeitung der Methoden entry()
, doing()
und exit()
an das jeweilige Zustandsobjekt. Die rudimentäre Ausgabe des Programms verrät uns, welche Klasse sich jeweils um die Methoden gekümmert hat:
<<< Entering state AnmeldenUI
--- Being in state AnmeldenUI
>>> Leaving state AnmeldenUI
<<< Entering state ListUI
--- Being in state ListUI
Nachdem die Grundzüge funktionieren können wir uns im Folgenden um eine Umgruppierung der einzelnen Zustandsklassen kümmern.
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: Aufbau des Patterns und Navigationsstruktur” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/state-pattern-cli1 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]