Manchmal möchte man in seinem Programm eine simple Zustandsmaschine nutzen, wobei man nicht auf bestehende Lösungen zurückgreifen möchte - weil sie zu unflexibel oder zu groß sind. Eine solche Zustandsmaschine kann dann auf verschiedenen Arten implementiert werden - häufig werden dabei verschachtelte Switch-Blöcke verwendet. Das vorliegende Beispiel zeigt eine andere, sinnvollere Implementierung auf Basis einer Schleife, die über Zustand-Übergangs-Klassen iteriert.
„State machine Beispiel” zeigt ein Beispiel einer solchen Zustandsmaschine, die Zustandsübergänge für ein Request-Objekt kontrollieren und anwenden soll:
Für unsere simple Zustandsmaschine implementieren wir - von Hilfsklassen abgesehen - nun drei Klassen:
- Die Zustandsmaschine
- Eine Klasse mit der Konfiguration
- Das eigentliche Request-Objekt
In der Klasse „StateMachine” steckt die ganze Logik: Hier wird schlicht über die hinterlegte Konfiguration iteriert. Wenn der gewünschte Zustandsübergang gefunden wurde, wird die entsprechende Status-Transition durchgeführt.
(Als Verbesserung könnte auch eine entspreche Fehlermeldung geworfen werden, sollte die gewünschte Transition nicht möglich sein.)
public class StateMachine {
private List<Transition> transitions;
private Request request;
public static StateMachine build(TransitionConfiguration config,
Request request) {
return new StateMachine(config, request);
}
private StateMachine(TransitionConfiguration config, Request request) {
this.request = request;
this.transitions = config.getTransitions();
}
// Apply a new action to the current request
public StateMachine apply(Action action) {
for (Transition transition : transitions) {
boolean currentStateMatch = transition.from
.equals(request.getState());
boolean conditionsMatch = transition.action
.equals(action);
if (currentStateMatch && conditionsMatch) {
System.out.println("Applying " + transition.to);
request.setState(transition.to);
break;
}
}
return this;
}
}
Die Konfiguration der Zustandsübergänge ist im „TransitionConfiguration” enthalten:
public enum TransitionConfiguration {
DEFAULT(Lists.newArrayList(
new Transition(State.DRAFT,
Action.SUBMIT_REQUEST,
State.IN_APPROVAL),
new Transition(State.IN_CHANGE,
Action.RESUBMIT_REQUEST,
State.IN_APPROVAL),
new Transition(State.IN_APPROVAL,
Action.APPROVE_REQUEST,
State.APPROVED),
new Transition(State.IN_APPROVAL,
Action.ASK_FOR_CHANGE,
State.IN_CHANGE)
));
private List<Transition> transitions;
TransitionConfiguration(List<Transition> transitions) {
this.transitions = transitions;
}
public List<Transition> getTransitions() {
return transitions;
}
}
Das Code-Beispiel „Request” zeigt das Request-Objekt, das seinen aktuellen Status selbst vorhält:
public class Request {
private String objectName;
private State state;
public Request(String objectName, State currentState) {
this.objectName = objectName;
this.state = currentState;
}
// setter/getter...
}
Das vollständige Beispiel ist hier zu finden.
Die Vorteile dieser Implementierung sind vielfältig:
- Es wird keine separate Entität benötigt, um den aktuellen Objekt-Zustand zu speichern, der Zustand ist im Objekt (Request) selbst enthalten und kann mit diesem persistiert werden.
- Die Konfiguration der Status-Übergänge ist als Programmcode vorhanden. Damit können diese einfach in Unit Tests überprüft werden. Außerdem werden Fehler schon zur Compile-Zeit sichbar.
- Eine Erweiterung der Statemachine ist einfach möglich. Z.B. könnte die gewünschte Transition geprüft werden oder das Transition-Objekt könnte weitere Eigenschaft wie Vorbedigungen/Nachbedigungen enthalten, die die Statemachine ausführt.
- Die Zustandsmaschine muß kein Wissen über die möglichen Übergänge/Aktionen etc. besitzen.
- Damit sind keine ewig langen switch-Anweisungen für die Statusübergänge nötig.
- Die Zustandsmaschine kann sehr simpel gehalten werden
Ein anderes Beispiel für das Prinzip dieser Implementierung könnte eine Permission-Machine sein, die anstatt von Transitionen eine Liste von Permissions erhält. Dort wiederum könnte man Operation auf USer oder Domänen abbilden, die von der PermissionMachine überprüft werden.