State Pattern-CLI: Realisierung von Transitionen und Menüstruktur

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

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

State-Pattern, Zustandsmuster, Transition, Trigger, UML-Zustandsdiagramm, UML-Klassendiagramm, Java, Kommandozeile, Menü

tl/dr; (ca. 17 min Lesezeit): Am Beispiel eines Command-Line-Interfaces wird das State-Pattern beispielhaft in Java implementiert – nachdem die Grundstruktur und die Zustands-ENUMS in den vorigen Teilen erstellt wurden, geht es jetzt um die objektorientierte Realisierung der Transitionen und Menüstruktur [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Der Artikel ist Bestandteil einer mehrteiligen Reihe:

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

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Dieser Artikel baut auf der Kann-Liste der Artikel State-Pattern-CLI Teil 1 und Teil 2 auf.

Als Grundlagen zu Transitionen im State-Pattern kann ich nach Durchlesen des Artikels…

  • … die Bestandteile einer Transition im UML-Zustandsdiagramm (Trigger, Guard, Effekt, Ausgangs- und Zielzustand) nennen.

  • … eine Klasse entwerfen, die einen Zustandsübergang mit Zielzustand, Trigger und Menütext modelliert.

  • … erklären, wie die Klasse Transitions die Transitionen eines Zustands verwaltet, zu einem Trigger die passende Transition sucht und sie ausführt.

  • … beschreiben, in welcher Reihenfolge exit() und entry() bei einem Zustandswechsel aufgerufen werden.

  • … begründen, warum neue Methoden im Interface deklariert werden müssen, wenn sie über eine Interface-Referenz aufgerufen werden.

  • … die Transitionen eines Zustandsdiagramms den Zustandsklassen zuordnen (initializeTransitions()).

Darüber hinaus kann ich als Expertenwissen…

  • … eine Eingabeschleife für eine Konsolenanwendung mit Eingabe, Triggerauswertung und Zustandswechsel implementieren.

  • … aus den Transitionen eines Zustands automatisch ein Menü erzeugen.

Das bisherige Grundgerüst

Es soll eine Kommandozeilen-Benutzeroberfläche für ein Programm erstellt werden, das mit folgendem Zustandsdiagramm modelliert wurde:

Zustandsdiagramm der Kommandozeilen-Benutzerführung

In der bisherigen Implementierung wurde bereits das Grundgerüst des State-Diagramms implementiert und die Zustandsobjekte in einem ENUM gekapselt:

UML-Klassendiagramm: Der Kontext UIContext aggregiert das Enum UIStateEnum, das die Zustandsobjekte AnmeldenUI und ListUI kapselt (mit KI erstellt)

Zur Orientierung nochmal im Schnelldurchlauf die Aufgaben der bisherigen Klassen/Interfaces:

  • eine Kontext-Klasse UIContext, die das Objekt repräsentiert, dessen Zustand sich ändert,

  • einige Zustandsklassen (beispielhaft AnmeldenUI und ListUI), die das Verhalten in bestimmten Zuständen implementieren,

  • die ENUM-Klasse (UIStateEnum), die die einzelnen Zustandsobjekte als Singletons zentral verwaltet,

  • ein gemeinsames Interface UIStateInterface der Zustandsklassen, das die Austauschbarkeit der Zustände ermöglicht und

  • eine abstrakte Klasse AbstractUIState, die gemeinsame Implementierungen aller Zustandsklassen umsetzt.

Transitionen erweitern

Bislang erfolgt der Zustandsübergang nur fest implementiert in der Kontext-Klasse – das entspricht natürlich nicht dem Wesen einer Benutzeroberfläche. Daher soll das Projekt jetzt auf objektorientierte Art mit den Zustandsübergängen (Transitionen) erweitert werden. Hierzu sind einige Anpassungen fällig:

  • Wir brauchen eine Klasse Transition, die einen einzelnen Zustandsübergang und dessen Eigenschaften erfasst (Transitionsziel, auslösende Trigger, auszuführende Effekte, Eintrag im Menü).

  • Eine weitere Klasse (Transitions) soll die unterschiedlichen Transitionen eines Zustands aggregieren und mögliche Transitionen des aktiven Zustands als Menü ausgeben.

  • Jede Zustandsklasse soll mit Transitions über so ein Aggregat der möglichen Zustandsübergänge verfügen. Es wird also ein Attribut vom Typ Transitions in AbstractUIState eingefügt.

OOP-Entwurf zur Umsetzung von Transitionen (UML-Klassendiagramm von Transition, Transitions, AbstractUIState)

Im Einzelnen:

Die Klasse Transition, die die Eigenschaften eines Zustandsübergangs modelliert

Im UML-Zustandsdiagramm bestehen Transitionen aus drei Komponenten:

  • ein Event (Trigger), das den Zustandsübergang auslöst,

  • ein Guard (Bedingung), der festlegt, unter welchen Bedingungen ein Zustandsübergang stattfinden darf,

  • ein Effekt, also eine Aktivität, die beim Zustandsübergang ausgeführt wird.

  • Natürlich ist eine Transition zudem charakterisiert durch den Ausgangs- und Zielzustand.

Wir vereinfachen zunächst etwas und erstellen unsere Klasse mit drei Attributen (sowie zugehörigem Getter und Konstruktor):

Die Klasse Transition als UML-Klassendiagramm

Es sind drei Attribute je Transition vorgesehen:

  • Der Zielzustand target (in Form des Enum-Objekts),

  • ein Trigger in Form des Zeichens (Taste), deren Eingabe den Zustandsübergang einleitet,

  • ein beschreibender Text für das Menü oder die Hilfe, der den Zustandsübergang benennt/beschreibt.

Um später ein Menü mit allen möglichen Zustandsübergängen generieren zu können, lassen wir uns alle Informationen in einer Zeile ausgeben und implementieren daher eine toString()-Methode.

Der Quelltext dieser Klasse sieht folgendermaßen aus:

public class Transition{
        private UIStateEnum target;
        private String trigger;
        private String menueItem;

        public Transition(UIStateEnum target, String trigger, String menueItem){
            this.target = target;
            this.trigger=trigger;
            this.menueItem=menueItem;
        }

        public UIStateEnum getTarget() {return target;}

        public String getTrigger() {return trigger;}

        public String getMenueItem() {return menueItem;}

        public String toString(){
            return "'"+getTrigger()+"': "+getMenueItem()+" ("+getTarget()+")";
        }
    }

Zusammenfassung aller Transitionen eines Zustands in der Klasse Transitions

Damit sich die Zustandsklassen nicht selbst um alle zugehörigen Transitionen kümmern müssen, wird diese Funktionalität ausgelagert in die Klasse Transitions. Diese Klasse

  • speichert, für welchen Ausgangszustand source sie zuständig ist,

  • sammelt alle Transition-Objekte dieser Zustandsklasse in einer ArrayList (transitionList),

  • sucht auf Basis eines Triggers (hier: eine Zeichenkette), ob dieser eine Transition auslöst (findTransition(...)),

  • führt gefundene Transitionen durch und gibt den neuen Zustand zurück (transist(...)).

Die genannten Funktionen lassen sich wie folgt modellieren:

UML-Klassendiagramm: Die Klasse Transitions mit source, transitionList und den Methoden addTransition(), getMenue(), findTransition() und transist() aggregiert Transition-Objekte

Die Implementierung könnte etwa so aussehen:

public class Transitions {
    private UIStateEnum source;

    public Transitions(UIStateEnum source){
        this.source = source;
    }

    private ArrayList<Transition> transitionList = new ArrayList<>();

    public void addTransition(UIStateEnum target, String trigger, String menueItem){
        transitionList.add(new Transition(target, trigger, menueItem));
    }
}

Die beiden weiteren Methoden dieser Klasse verdienen etwas mehr Aufmerksamkeit:

Gibt es für den vorliegenden Trigger (die eingegebene Zeichenkette) eine zuständige Transition? Das prüft findTransition(...):

public Transition findTransition(String actualTrigger){
        System.out.println("Prüfe, ob der Trigger <"+actualTrigger+"> eine Transition auslöst:");
        for(Transition t : transitionList){
            System.out.println(" - Prüfe Transition "+t.getTarget().name()+" mit Trigger <" + t.getTrigger()+">");
            if (t.getTrigger().equals(actualTrigger)){
                System.out.println(" ==> Transition zu "+t.getTarget().name()+" gefunden! (Weitere Suche wird gestoppt)");
                return t;
        }   }
        return null;
    }

transist(...) führt die Transition durch:

  • Ist überhaupt eine Änderung des Zustands erfolgt, oder bleibt alles beim Alten?

  • Starte die Aktivität beim Verlassen des alten Zustands (exit()).

  • Starte die Aktivität beim Betreten des neuen Zustands (entry()).

  • Gib den aktuellen Zustand an das aufrufende Modul zurück.

Implementiert ist das folgendermaßen:

public UIStateEnum transist(Transition transition){

        if ((transition!= null) && (transition.getTarget() != null)){
            source.getUIState().exit();
            transition.getTarget().getUIState().entry();
            return transition.getTarget();
        }else{
            System.out.println("Keine Transition gefunden! Bleibe bei "+source.getUIState().getClass().getSimpleName());
            return source;
        }
    }

Anpassungen der abstrakten Klasse AbstractUIState

In den Zustandsklassen sollen die Transitionen über Transitions aggregiert werden. Dazu ist ein Attribut transitions nötig, eine Methode, die ein neues Transitionsobjekt anlegt (initializeTransitions()) sowie eine Methode, die neue Transitionen für das jeweilige Zustandsobjekt zu transitions hinzufügt (addTransition()):

private Transitions transitions;

public void initializeTransitions() {
    transitions = new Transitions(UIStateEnum.getUIStateEnum(this));
}

public void addTransition(UIStateEnum target, String trigger, String menueItem) {
          this.transitions.addTransition(target, trigger, menueItem);
}

Jedes Zustandsobjekt soll zukünftig selbst prüfen, ob ein Trigger (hier vereinfacht: ein Tastendruck bzw. eine übergebene Zeichenkette) einen Zustandswechsel hervorruft. Dazu benötigen wir eine weitere Methode, die die Transitionen sucht und ausführt: handleTriggerEvent():

public UIStateEnum handleTriggerEvent(String trigger) {
    Transition foundTransition = this.transitions.findTransition(trigger);
    return this.transitions.transist(foundTransition);
}

Ergänzungen des Interfaces

Die Methoden handleTriggerEvent() und initializeTransitions() müssen für alle Zustandsobjekte über das Interface aufrufbar sein. In Java bestimmt der Instanztyp, welches Verhalten genutzt wird; es können aber nur Methoden aufgerufen werden, die der Typ der Referenz deklariert. Da der Aufruf über eine Interface-Referenz von UIStateInterface erfolgt, müssen wir die neuen Methoden dort aufnehmen:

public interface UIStateInterface {
  ...
  public UIStateEnum handleTriggerEvent(String trigger);
  public void initializeTransitions();
  ...
}

Die Änderungen an Interface und abstrakter Klasse verändern auch das UML-Klassendiagramm:

Das Interface UIStateInterface und die Klasse AbstractUIState mit den ergänzten Methoden initializeTransitions() und handleTriggerEvent(...)

Transitionen den Zuständen zuordnen

Jetzt folgt etwas Handarbeit: Wir müssen jeden einzelnen Zustandsübergang (Transition) bei allen Zustandsklassen einfügen (per addTransition()). Hierzu wird initializeTransitions() spezialisiert:

  • Zunächst wird das Transitionsobjekt erstellt, indem die Methode der Superklasse (AbstractUIState) aufgerufen wird.

  • Danach wird für jede gewünschte Transition einmal per addTransition() das Transitionsziel (per ENUM), die Trigger-Zeichenkette und ein Titel übergeben.

Im Fall der Zustandsklasse AnmeldenUI wäre dies:

public void initializeTransitions(){
    super.initializeTransitions();
    this.addTransition(UIStateEnum.LIST_UI, "l", "Anmelden");
}

Wenn alle im Zustandsdiagramm modellierten Zustandsklassen erstellt wurden, wird der Umfang bei ListUI deutlich größer – und die Vorteile einer einfachen Navigation per State-Pattern ersichtlich. Ich habe die betreffenden Transitions hier zunächst auskommentiert, da im Tutorial ja bislang nur ANMELDEN_UI und LIST_UI umgesetzt wurden – die anderen Verhaltensklassen müssen natürlich analog implementiert werden.

public void initializeTransitions() {
        super.initializeTransitions();
        this.addTransition(UIStateEnum.ANMELDEN_UI, "a", "Abmelden");
        /* 
        // Für diese Zustandsübergänge sind die weiteren im Zustands-Diagramm modellierten 
        // Klassen erforderlich - bitte nur entkommentieren, wenn diese bestehen!
        this.addTransition(UIStateEnum.CREATE_DETAIL_UI, "c", "Neuen Eintrag erstellen");
        this.addTransition(UIStateEnum.READ_DETAIL_UI, "r", "Details anzeigen");
        this.addTransition(UIStateEnum.UPDATE_DETAIL_UI, "u", "Eintrag ändern");
        this.addTransition(UIStateEnum.DELETE_DETAIL_UI, "d", "Eintrag löschen");
         */
    }

Damit das Gesamtsystem wieder in einem definierten Zustand ist, muss jetzt nur noch die Kontext-Klasse angepasst werden:

Anpassungen im Kontext

Gravierende Anpassungen ergeben sich auch am Kontext:

  • Zu Beginn muss jedes Zustandsobjekt einmal konfiguriert werden: die Methode initializeTransitions(); wird daher in der main()-Methode für jedes Zustandsobjekt einmal aufgerufen.

  • Danach wird das Kontext-Objekt erstellt,

  • der Ausgangszustand per setUiStateEnum() gesetzt und

  • eine Methode loop() als Dauerschleife gestartet, die ab jetzt alle weitere Logik übernimmt.

public static void main(String[] args) {
    for (UIStateEnum nextUiStateEnum : UIStateEnum.values()) {
        nextUiStateEnum.getUIState().initializeTransitions();
    }
    UIContext myUI = new UIContext();
    myUI.setUiStateEnum(UIStateEnum.ANMELDEN_UI);
    for(;;){
       myUI.loop();
    }
}

In dieser loop() wird

  • zunächst auf eine Eingabe auf der Konsole gewartet (readln() wird unten implementiert),

  • geprüft, ob diese Zeichenkette einen Zustandswechsel hervorruft (die Methode handleTriggerEvent() delegiert dies an das aktive Zustandsobjekt) und

  • der Zustandswechsel durchgeführt.

public void loop(){
    String keyStroke = readln();
    UIStateEnum uistate = handleTriggerEvent(keyStroke);
    setUiStateEnum(uistate);
}

Die Methode handleTriggerEvent() delegiert die Ausführung der Triggerauswertung – ganz wie im State-Pattern vorgesehen – an den aktiven Zustand:

public UIStateEnum handleTriggerEvent(String keyStroke){
    return uiStateEnum.getUIState().handleTriggerEvent(keyStroke);
}

Die Methode readln() schließlich gibt einfach die Eingabe in der Konsole zurück. Diese muss leider immer mit return (Zeilenumbruch) abgeschlossen werden.

private String readln() {
    String eingabe ="";
    InputStreamReader inReader = new InputStreamReader(System.in);

    try{
        BufferedReader buffReader = new BufferedReader(inReader);
        System.out.println("UI-Menu (Eingabe mit [ENTER] beenden)>"); //Eine Art Eingabeaufforderung - muss natürlich nicht sein
        while ("".equals(eingabe)){
          eingabe = buffReader.readLine();
        }
    } catch (IOException ex) {
        System.out.println("Es ist ein Fehler aufgetreten: "+ ex.getMessage());
    }

    return eingabe;
}

Hinweis: readln() erzeugt bei jedem Aufruf einen neuen BufferedReader. Für die interaktive Eingabe an der Konsole genügt das. Wird die Eingabe dagegen umgeleitet (z. B. java … < eingaben.txt), kann der erste BufferedReader mehr Zeilen auf Vorrat lesen, als er zurückgibt – diese Zeilen gehen verloren. Am Ende einer umgeleiteten Eingabe liefert readLine() außerdem null, und die Schleife meldet endlos, dass keine Transition gefunden wurde. Wer das Programm so testen will, legt den BufferedReader einmal als Attribut an und prüft auf null.

Ab diesem Punkt ist das System wieder benutzbar, aber noch nicht allzu komfortabel. Man muss auswendig wissen, mit welchem Trigger man welchen Zustand erreichen kann. Mit drei kleinen Änderungen können wir unsere Konsolen-App noch mit einem Menü versorgen – dann ist das schon fast eine runde Sache.

Beim Eintritt in einen Zustand ein Menü mit allen möglichen Transitionen ausgeben

Wir haben schon fast alle Informationen zusammen, die wir benötigen, um uns ein Menü auszugeben:

Die Klasse Transitions kennt alle zugelassenen Transitionen des aktiven Zustands – wir müssen sie nur noch mit einer neuen Methode aufbereiten:

public String getMenue(){
        String menue = "";
        for(Transition t : transitionList){
            menue += t.toString()+"\n";
        }
        return menue;
    }

Wenn uns Transitions diese Einträge liefert, können wir sie in AbstractUIState in einer neuen Methode aufbereiten und ausgeben:

public void showMenue(){
    int menueheader = 60;
    System.out.println();
    System.out.println("#".repeat( menueheader));
    int border = (menueheader - 2 - this.getClass().getSimpleName().length())/2;
    System.out.println("#".repeat(border) + " "+this.getClass().getSimpleName() +" " + "#".repeat(border));
    System.out.println("#".repeat( menueheader));
    System.out.println(transitions.getMenue());
}

Wir müssen showMenue() noch dem UIStateInterface hinzufügen, damit wir das Menü aus dem UIContext heraus erreichen können:

  public void showMenue();

Die Methode showMenue() können wir schließlich in entry() der Klasse AbstractUIState aufrufen, damit wir bei jedem Eintritt in einen neuen Zustand direkt ein Menü erhalten (beim Zustandswechsel ruft transist() die entry()-Methode des Zustandsobjekts auf, nicht die des Kontexts):

public void entry() {
    System.out.println("<<< Entering state " + this.getClass().getSimpleName());
    showMenue();
}

Damit wir direkt nach Programmstart ein Menü erhalten (denn bis dahin hat ja noch kein Zustandsübergang stattgefunden) müssen wir einmal das Menü händisch aufrufen. Ich habe dazu in der main()-Methode nach Setzen des Start-Zustands die Zeile myUI.uiStateEnum.getUIState().showMenue(); angefügt:

    UIContext myUI = new UIContext();
    myUI.setUiStateEnum(UIStateEnum.ANMELDEN_UI);
    myUI.uiStateEnum.getUIState().showMenue();

Das Programm testen

Wenn wir das Programm starten, meldet sich die Konsolenapp erwartungsgemäß mit einer Liste der möglichen Menüpunkte (Transitionen):

############################################################
######################## AnmeldenUI ########################
############################################################
'l': Anmelden (LIST_UI)

UI-Menu (Eingabe mit [ENTER] beenden)>

Eine Eingabe von l löst wunschgemäß die Transition aus (wir haben noch relativ viele println im Code, die das dokumentieren). Es wird das neue Menü ausgegeben.

Prüfe, ob der Trigger <l> eine Transition auslöst:
 - Prüfe Transition LIST_UI mit Trigger <l>
 ==> Transition zu LIST_UI gefunden! (Weitere Suche wird gestoppt)
>>> Leaving state AnmeldenUI
<<< Entering state ListUI

############################################################
########################## ListUI ##########################
############################################################
'a': Abmelden (ANMELDEN_UI)

UI-Menu (Eingabe mit [ENTER] beenden)>

Dargestellt habe ich hier das Menü mit den beiden Zuständen AnmeldenUI und ListUI. Spätestens jetzt ist aber ein guter Zeitpunkt, die übrigen Zustandsklassen und deren Transitionen anhand des oben abgebildeten UML-Zustandsdiagramms zu implementieren!

Das “Big Picture”

Am Ende der dritten Iteration ist schon relativ viel umgesetzt und wir haben eine funktionierende Navigation zwischen den unterschiedlichen Benutzerschnittstellen-Zuständen.

Das UML-Klassendiagramm des Gesamtsystems sieht mittlerweile schon recht umfangreich aus:

Klassendiagramm am Ende der 3. Iteration (mit KI erstellt)

Was bleibt noch zu tun?

Um das komplette Zustandsdiagramm abzubilden und einen Zustandsautomaten zu implementieren, fehlen aber noch einige Komponenten:

  • Transitionen bestehen aus Triggern, Guards und Effekten - bislang sind nur erstere implementiert.

  • Trigger können bislang nur für Zustandswechsel vergeben werden: innere Aktivitäten können darüber noch nicht ausgelöst werden.

  • Innere Zustände, parallele Zustände… es gibt viel zu tun in der…

… vierten Iteration!

Leitfragen

  1. Aus welchen Bestandteilen besteht eine Transition im UML-Zustandsdiagramm?

Eine Transition verbindet einen Ausgangs- mit einem Zielzustand und kann einen Trigger (Event), einen Guard (Bedingung) und einen Effekt (Aktivität beim Übergang) haben. In diesem Teil modelliert die Klasse Transition zunächst nur Zielzustand, Trigger und Menütext. siehe Abschnitt „Die Klasse Transition“

  1. Welche Aufgaben übernimmt die Klasse Transitions?

Sie speichert den Ausgangszustand, sammelt alle Transition-Objekte des Zustands in einer ArrayList, sucht zu einem Trigger die passende Transition (findTransition()), führt sie aus (transist()) und liefert die Menüeinträge (getMenue()). siehe Abschnitt „Zusammenfassung aller Transitionen eines Zustands“

  1. Was passiert in der Methode transist()?

Wurde eine Transition gefunden, ruft sie exit() des alten und danach entry() des neuen Zustands auf und gibt den neuen Zustand zurück. Ohne passende Transition bleibt der bisherige Zustand erhalten. siehe Abschnitt „Zusammenfassung aller Transitionen eines Zustands“

  1. Warum müssen handleTriggerEvent() und initializeTransitions() im Interface deklariert werden?

Die Zustandsobjekte werden über Referenzen vom Typ UIStateInterface angesprochen. Welche Implementierung läuft, bestimmt zwar das Objekt, aufrufen lassen sich aber nur Methoden, die der Referenztyp deklariert. siehe Abschnitt „Ergänzungen des Interfaces“

  1. Wie werden die Transitionen den einzelnen Zuständen zugeordnet?

Jede Zustandsklasse überschreibt initializeTransitions(): Der Aufruf der Superklassen-Methode erzeugt das Transitions-Objekt, danach wird jede Transition mit Zielzustand, Trigger und Menütext per addTransition() hinzugefügt. siehe Abschnitt „Transitionen den Zuständen zuordnen“

  1. Welche Aufgabe hat die Methode loop() des Kontexts?

Sie wartet auf eine Eingabe (readln()), lässt den aktiven Zustand per handleTriggerEvent() prüfen, ob die Eingabe eine Transition auslöst, und setzt den zurückgegebenen Zustand. Die main()-Methode ruft loop() in einer Endlosschleife auf. siehe Abschnitt „Anpassungen im Kontext“

  1. Wie entsteht das Menü der Konsolenanwendung?

getMenue() setzt die toString()-Ausgaben aller Transitionen eines Zustands zusammen, showMenue() gibt sie mit einer Kopfzeile aus. Aufgerufen wird showMenue() bei jedem Eintritt in einen Zustand und einmal nach dem Programmstart. siehe Abschnitt „Beim Eintritt in einen Zustand ein Menü ausgeben“


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: Realisierung von Transitionen und Menüstruktur” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/state-pattern-cli3 veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/design-pattern/entwurfsmuster. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[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: