Wir wollen unsere Freundin (oder wahlweise den Freund) modellieren. Wer denkt, dies sei praxisfern, der irrt. Man denke nur an das erfolgreiche PC-Spiel "Die Sims". Die folgende Modellierung könnte dort realisiert worden sein.
Nun, wir können mit unserer Freundin interagieren: Wir können uns mit ihr unterhalten, ihr einen Kuss geben oder sie ärgern. Je nach ausgeführter Aktion wird sich ihre Laune (ihr Zustand) ändern: Neutral, Bockig oder Fröhlich. Küsst man eine neutralgelaunte Freudin, wird sie fröhlich. Ärgert man sie, wird sie bockig.
Die Zustände und die Zustandsübergänge unserer Freundin seien in folgendem Zustandsautomat illustriert:

Mit einem Kuss ist alles wieder in Ordnung. Wenn es doch in der Wirklichkeit nur so einfach wäre. ;-)
Je nach Zustand wird die Freundin unterschiedlich auf Interaktionen (unterhalten(), verärgern(), kussGeben()) reagieren. Die Realität lehrt uns, dass es sich mit einer bockigen Freundin anders unterhält, als mit einer fröhlichen. Das äußere Verhalten der Freundin ändert sich also in Abhängigkeit von ihrem inneren Zustand.
Versetzen wir uns gedanklich in die 1970er Jahre: zur Blütezeit der Strukturierten Programmierung. Wie hätte man damals das Problem gelöst? Der aktuelle Zustand würde durch eine Integer-Variable repräsentiert werden. 0 steht für Neutral, 1 für Bockig und 2 für Fröhlich. In jeder Operation (unterhalten(), kussGeben(), verärgern()) wird zunächst geprüft, welchen Wert diese Integer-Variable hat und entsprechend wird ein Verhalten ausgeführt.

Code der Klasse Freundin:
class Freundin{
//Mögliche Launen der Freudin
private static final int NEUTRAL = 0;
private static final int BOCKIG = 1;
private static final int FRÖHLICH = 2;
//Zahl repräsentiert aktuellen Zustand
private int aktuellerZustand;
//Zustandsabhängiges Verhalten
public void unterhalten(){
if (aktuellerZustand == NEUTRAL){
//NEUTRAL-spezifisches Verhalten...
System.out.println("Fününününü.");
} else if (aktuellerZustand == BOCKIG){
//BOCKIG-spezifisches Verhalten...
System.out.println("Fahr jetzt nach Hause! Ich will nicht mit dir reden!");
} else if (aktuellerZustand == FRÖHLICH){
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, Fünüüüüüüünü!");
}
}
public void kussGeben(){
if (aktuellerZustand == NEUTRAL){
//NEUTRAL-spezifisches Verhalten...
System.out.println("Hihi :-)");
aktuellerZustand = FRÖHLICH; //Zustandsänderung!
} else if (aktuellerZustand == BOCKIG){
//BOCKIG-spezifisches Verhalten...
System.out.println("Na gut! Hab dich wieder lieb.");
aktuellerZustand = NEUTRAL; //Zustandsänderung!
} else if (aktuellerZustand == FRÖHLICH){
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, :-D");
}
}
public void verärgern(){
if (aktuellerZustand == NEUTRAL){
//NEUTRAL-spezifisches Verhalten...
System.out.println("Du spinnst wohl! Ich bin sauer! ;-(");
aktuellerZustand = BOCKIG; //Zustandsänderung!
} else if (aktuellerZustand == BOCKIG){
//BOCKIG-spezifisches Verhalten...
System.out.println("Du machst alles bloß noch schlimmer!");
} else if (aktuellerZustand == FRÖHLICH){
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Du spinnst wohl! ;-(");
aktuellerZustand = BOCKIG; //Zustandsänderung!
}
}
}
Es funktioniert zweifellos. Jedoch hat es seinen Grund, dass die reine strukturierte Programmierung durch die Objektorientierung verdrängt wurde:
Durch diese strukturierte Denke verletzen wir zahlreiche OO-Entwurfsprinzipien:
Identifiziere jene Aspekte, die sich ändern und trenne sie von jenen, die konstant bleiben.
Wahrscheinlich müssen in Zukunft neue Zustände integriert werden, die Freundin an sich bleibt dabei jedoch konstant. Bei unserem jetzigem Entwurf muss bei jeder Änderung der Code der Freundin angefasst werden.
Offen/Geschlossen-Prinzip (Open/Closed):
Entwürfe sollten für Erweiterungen offen, aber für Veränderungen geschlossen sein.
Diese Prinzip fordert letztlich das selbe: Der Freundincode soll für Veränderungen geschlossen sein, aber sie soll um neue Zustände jederzeit erweitert werden können.
Kohäsion (= Grad, inwiefern eine Klasse einen einzigen konzentrierten Zweck hat) und Delegation:
Kapsele Verantwortlichkeiten in eigenen Objekten und delegiere Aufrufe an diese Objekte. Es gilt: eine Klasse, eine Verantwortlichkeit.
Gerade das letzte Entwurfsprinzip macht eins deutlich: Die Zustände müssen in eigenen Objekten gekapselt werden.
Schauen wir uns doch noch einmal die Methoden unterhalten(), kussGeben() und verärgern() an. Sie haben alle eine ähnliche Struktur von Bedingungsanweisungen. Nun liegt es Nahe, alle Bedingungszweige, der logisch zu einem Zustand gehört, auch in ein gemeinsames Objekt (das Zustandsobjekt) zu übertragen. Damit enthält jedes entstandene Zustandsobjekt das Verhalten für diesen Zustand.

Zum Vergleich: Alter Freundincode. Strukturgleiche Methoden mit Fallunterscheidungen:
public void unterhalten() {
if (aktuellerZustand == NEUTRAL) {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Fününününü.");
}
else if (aktuellerZustand == BOCKIG) {
//BOCKIG-spezifisches Verhalten...
System.out.println("Fahr jetzt nach Hause! Ich will nicht mit dir reden!");
}
else if (aktuellerZustand == FRÖHLICH) {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, Fünüüüüüüünü!");
}
}
public void kussGeben() {
if (aktuellerZustand == NEUTRAL) {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Hihi :-)");
aktuellerZustand = FRÖHLICH; //Zustandsänderung!
}
else if (aktuellerZustand == BOCKIG) {
//BOCKIG-spezifisches Verhalten...
System.out.println("Na gut! Hab dich wieder lieb.");
aktuellerZustand = NEUTRAL; //Zustandsänderung!
}
else if (aktuellerZustand == FRÖHLICH) {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, :-D");
}
}
public void verärgern() {
if (aktuellerZustand == NEUTRAL) {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Du spinnst wohl! Ich bin sauer! ;-(");
aktuellerZustand = BOCKIG; //Zustandsänderung!
}
else if (aktuellerZustand == BOCKIG) {
//BOCKIG-spezifisches Verhalten...
System.out.println("Du machst alles bloß noch schlimmer!");
}
else if (aktuellerZustand == FRÖHLICH) {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Du spinnst wohl! ;-(");
aktuellerZustand = BOCKIG; //Zustandsänderung!
}
}
Nun kapselt die drei Zustandsklassen Neutral, Bockig und Fröhlich das zustandsspezifische Verhalten der Freundin:
class Neutral {
public void unterhalten() {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Fününününü.");
}
public void kussGeben() {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Hihi :-)");
//Zustandsänderung zum Zustand "Fröhlich". Dazu später.
}
public void verärgern() {
//NEUTRAL-spezifisches Verhalten...
System.out.println("Du spinnst wohl! Ich bin sauer! ;-(");
//Zustandsänderung zum Zustand "Bockig". Dazu später.
}
}
class Bockig {
public void unterhalten() {
//BOCKIG-spezifisches Verhalten...
System.out.println("Fahr jetzt nach Hause! Ich will nicht mit dir reden!");
}
public void kussGeben() {
//BOCKIG-spezifisches Verhalten...
System.out.println("Na gut! Hab dich wieder lieb.");
//Zustandsänderung zum Zustand "Neutral". Dazu später.
}
public void verärgern() {
//BOCKIG-spezifisches Verhalten...
System.out.println("Du machst alles bloß noch schlimmer!");
}
}
class Fröhlich {
public void unterhalten() {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, Fünüüüüüüünü!");
}
public void kussGeben() {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Hihi, :-D");
}
public void verärgern() {
//FRÖHLICH-spezifisches Verhalten...
System.out.println("Du spinnst wohl! ;-(");
//Zustandsänderung zum Zustand "Bockig". Dazu später.
}
}
Die Zustandsklassen haben die selbe Schnittstelle, wie die Freundin (unterhalten(), kussGeben(), verärgern()). Die Freundin aggregiert fortan ein solches Zustandsobjekt (= aktueller Zustand) und delegiert Aufrufe an dieses Zustandsobjekt. Dazu wird eine Schnittstelle (hier Interface IZustand) für die Zustände eingeführt.
Die "neue" Freundin: Aufruf-Delegation an das aktuell gesetzte Zustandsobjekt:
class Freundin {
//Membervariable mit dem aktuellem Zustand
private IZustand aktuellerZustand;
//Die "neue" Freundin delegiert die Aufruf an ihren aktuellen Zustand
public void unterhalten() {
aktuellerZustand.unterhalten();
}
public void kussGeben() {
aktuellerZustand.kussGeben();
}
public void verärgern() {
aktuellerZustand.verärgern();
}
}

Soweit, so gut. Bleibt nur eine Frage offen: Wie wechselt die Freundin ihre Zustände? Eine denkbar einfache Vorgehensweise ist dabei, das gewünschte Zustandsobjekt bei jedem Zustandswechsel neu zu instanziieren. Dies ist natürlich in unserem Fall (häufige Zustandswechsel) nicht sonderlich klug, jedoch sei es dennoch aus Gründen der Einfachheit und Didaktik realisiert. Die Problematik der Zustandswechsel wird unter Variationen näher diskutiert.
Damit die Zustandsobjekte selbstständig den Zustand der Freundin wechseln können, benötigen sie eine Referenz auf die Freundin. Weiterhin muss die Freundin um einen Setter zum Setzen des gewünschten Zustands erweitert werden und ein mit der Freundin parametrisierter Konstrukur für die Zustände definiert werden, damit die Zustände die Freundin kennen und den aktuellen Zustand der Freundin setzen können.
Der angepasste Entwurf sieht wie folgt aus:
Freundin mit erweiterter Schnittstelle zum Setzen des Zustands:
class Freundin {
//Setter zum Setzen des aktuellen Zustands.
public void setAktuellerZustand(IZustand pAktuellerZustand){
aktuellerZustand = pAktuellerZustand;
}
//Defaultzustand Neutral im Konstruktor setzen.
public Freundin(){
setAktuellerZustand(new Neutral(this));
}
//Rest wie gehabt.
private IZustand aktuellerZustand;
public void unterhalten() {
aktuellerZustand.unterhalten();
}
public void kussGeben() {
aktuellerZustand.kussGeben();
}
public void verärgern() {
aktuellerZustand.verärgern();
}
}
Erweiterte Zustandsklassen, die selbstständig den Zustandswechsel vollziehen:
interface IZustand{
public void unterhalten();
public void kussGeben();
public void verärgern();
}
class Neutral implements IZustand{
//Konstruktur. Mit Freundin parametrisiert
public Neutral(Freundin pFreundin){
_freundin = pFreundin;
}
//Referenz auf die Freundin
private Freundin _freundin;
public void unterhalten() {
System.out.println("Fününününü.");
}
public void kussGeben() {
System.out.println("Hihi :-)");
_freundin.setAktuellerZustand(new Fröhlich(_freundin)); //Zustandsübergang
}
public void verärgern() {
System.out.println("Du spinnst wohl! Ich bin sauer! ;-(");
_freundin.setAktuellerZustand(new Bockig(_freundin)); //Zustandsübergang
}
}
class Bockig implements IZustand{
//Konstruktur. Mit Freundin parametrisiert
public Bockig(Freundin pFreundin){
_freundin = pFreundin;
}
//Referenz auf die Freundin
private Freundin _freundin;
public void unterhalten() {
System.out.println("Fahr jetzt nach Hause! Ich will nicht mit dir reden!");
}
public void kussGeben() {
System.out.println("Na gut! Hab dich wieder lieb.");
_freundin.setAktuellerZustand(new Neutral(_freundin)); //Zustandsübergang
}
public void verärgern() {
System.out.println("Du machst alles bloß noch schlimmer!");
}
}
class Fröhlich implements IZustand{
//Konstruktur. Mit Freundin parametrisiert
public Fröhlich(Freundin pFreundin){
_freundin = pFreundin;
}
//Referenz auf die Freundin
private Freundin _freundin;
public void unterhalten() {
System.out.println("Hihi, Fünüüüüüüünü!");
}
public void kussGeben() {
System.out.println("Hihi, :-D");
}
public void verärgern() {
System.out.println("Du spinnst wohl! ;-(");
_freundin.setAktuellerZustand(new Bockig(_freundin)); //Zustandsübergang
}
}
Besonders schön lässt sich unser Entwurf nun durch den Client verwenden. Der Client hat keine Kenntnis von den Zuständen und Zustandswechsel der Freundin. Er kennt allein das Freundinobjekt und interagiert ausschließlich mit diesem. Dabei wechselt die Freundin zur Laufzeit dynamisch ihr Verhalten, ohne dass der Client davon etwas mitbekommt oder gar etwas dazu beiträgt. Er scheint so, als hätte die Freundin ihre Klasse geändert.
Benutzung durch den Client:
Freundin freundin = new Freundin();
//Defaultzustand: Neutral
freundin.unterhalten(); //Fününününü.
freundin.verärgern(); //Du spinnst wohl! Ich bin sauer! ;-(
//Ab jetzt: Bockig
freundin.unterhalten(); //Fahr jetzt nach Hause! Ich will nicht mit dir reden!
freundin.unterhalten(); //Fahr jetzt nach Hause! Ich will nicht mit dir reden!
freundin.kussGeben(); //Na gut! Hab dich wieder lieb.
//Ab jetzt: Neutral
freundin.kussGeben(); //Hihi :-)
//Ab jetzt: Fröhlich
freundin.unterhalten(); //Hihi, Fünüüüüüüünü!
Voila! Unsere neue Freundin kann ihr Verhalten dynamisch in Abhängigkeit von ihrem inneren Zustand verändern. Damit bringt sie zahlreiche Vorzüge mit sich:
Es zeigt sich, dass durch den Einsatz des State Patterns, ein hohes Maß an Flexibilität und Dynamik gewonnen wird, während zeitgleich die Wartung erleichtert wird und Erweiterungen schnell und unkompliziert möglich sind.
Nach dieser Einführung wird im folgenden Abschnitt das State Design Pattern formalisiert, näher analysiert und diskutiert.
| Klasse | State Teilnehmer |
|---|---|
| Freundin | Kontext (Context) |
| Neutral, Bockig, Fröhlich | Konkrete Zustände (Concrete States) |
State:
"Ermögliche es einem Objekt, sein Verhalten zu ändern, wenn sein interner Zustand sich ändert. Es wird so aussehen, als ob das Objekt seine Klasse gewechselt hat."
([GoF], Seite 398)
Das State Entwurfsmuster ermöglicht die elegante Modellierung von zustandsabhängigen Verhalten eines Objekts. Je nach internen Zustand ändert sich das Verhalten des Objekts.
Es wird eine einheitliche Schnittstelle für die möglichen Zustände definiert (IState). Für jeden Zustand wird eine Klasse erstellt, die diese Schnittstelle realisiert (ConcreteStateX).
Das Objekt, dessen Verhalten in Abhängigkeit vom Zustand geändert werden soll (das Context-Objekt) aggregiert nun ein solches Zustandsobjekt (via Instanzvariable). Dieses Objekt repräsentiert den aktuellen internen Zustand und kapselt das zustandsabhängige Verhalten des Contexts. Der Context delegiert Aufrufe an sein aktuell gesetztes Zustandsobjekt.
Die Zustandswechsel und -übergänge können durch die konkreten Zustände selbst durchgeführt werden, in dem sie dem Context einen Folgezustand zu weisen. Mehr dazu unter Variationen.

Context:
class Context{
private IState state;
public void operate(){
state.operate();
}
}
IState-Interface und Implementierungen:
interface IState{
public void operate();
}
class ConcreteState1 implements IState{
public void operate() {
System.out.println("ConcreteState1");
//ggf. Zustandswechsel, dazu Handle auf Context mit entsprechenden Setter notwendig
}
}
class ConcreteState2 implements IState{
public void operate() {
System.out.println("ConcreteState2");
//ggf. Zustandswechsel, dazu Handle auf Context mit entsprechenden Setter notwendig
}
}
class ConcreteState3 implements IState{
public void operate() {
System.out.println("ConcreteState3");
//ggf. Zustandswechsel, dazu Handle auf Context mit entsprechenden Setter notwendig
}
}
Es muss definiert werden, wo entschieden wird, welcher Zustand als nächstes gesetzt werden soll. Dies kann zu einem in den konkreten Zustandsobjekten, aber auch im Kontextobjekt geschehen.
Zustände bestimmen Folgezustand
Damit jeder Zustand seinen Folgezustand bestimmen kann, muss er diesem beim Contextobjekt setzen. Dazu benötigt er aber
Damit die Zustandsobjekte nicht immer wieder mit new neu instanziiert werden müssen, kann der Context alle möglichen Zustände als Attribute halten und Zugriff über Getter ermöglichen. Dies ist ein Versuch die Abhängigkeiten zwischen den Zustandsobjekten zu reduzieren.
Contextobjekt stellt alle nötigen Schnittstellen bereit, damit Zustände Zugriff auf den aktuellen Zustand haben und diesen neu setzen können:
class Context{
private IState _currentState;
public void operate(){
_currentState.operate();
}
//Setter zum Setzen des aktuellen Zustands
public void setState(IState pState){
_currentState = pState;
}
//Context hält alle möglichen Zustände vor
private IState _concreteState1;
private IState _concreteState2;
private IState _concreteState3;
//Getter für die möglichen Zustände
public IState getConcreteState1() {
return _concreteState1;
}
public IState getConcreteState2() {
return _concreteState2;
}
public IState getConcreteState3() {
return _concreteState3;
}
//Initialisierung in Konstruktor
public Context(){
_concreteState1 = new ConcreteState1(this);
_concreteState2 = new ConcreteState2(this);
_concreteState3 = new ConcreteState3(this);
_currentState = _concreteState1;
}
}
Zustände setzen Folgezustand selbst:
_context.setState(_context.getConcreteState3());
Vollständige Code der Zustände:
interface IState{
public void operate();
}
class ConcreteState1 implements IState{
//Zustand muss Context zum Setzen des Folgezustands kennen.
private final Context _context;
//Konstruktor
public ConcreteState1(Context pContext) {
_context = pContext;
}
public void operate() {
System.out.println("ConcreteState1");
//Zustandswechsel, ggf. mit Bedingung
_context.setState(_context.getConcreteState2());
}
}
class ConcreteState2 implements IState{
//Zustand muss Context zum Setzen des Folgezustands kennen.
private final Context _context;
//Konstruktor
public ConcreteState2(Context pContext) {
_context = pContext;
}
public void operate() {
System.out.println("ConcreteState2");
//Zustandswechsel, ggf. mit Bedingung
_context.setState(_context.getConcreteState3());
}
}
class ConcreteState3 implements IState{
//Zustand muss Context zum Setzen des Folgezustands kennen.
private final Context _context;
//Konstruktor
public ConcreteState3(Context pContext) {
_context = pContext;
}
public void operate() {
System.out.println("ConcreteState3");
//Zustandswechsel, ggf. mit Bedingung
_context.setState(_context.getConcreteState1());
}
}
Eine Alternative wäre es, die Zustände als Singletons zu realisieren. Damit müsste der Context nicht alle möglichen Zustände und die dafür notwendigen Getter bereitstellen. Dies ist aber nur möglich, wenn die Zustandsobjekte über keine Attribute und Daten verfügen. Eine andere Lösung wäre, die Zustände immer erst bei Bedarf zu erzeugen und danach gleich wieder zu löschen, so dass keine Referenz auf diese gehalten werden muss.
Durch diese Vorgehensweise wird der Entwurf sehr flexibel und dynamisch. Zustandswechsel können beliebig durchgeführt werden. Der Context ist von dieser Verantwortung befreit. Somit können Änderungen an den Zustandsübergängen durchgeführt werden, ohne den Context modifzieren zu müssen.
Context bestimmt Folgezustände
Die Verantwortung für die Zustandsübergänge beim Contextobjekt zu implementieren ist sinnvoll, wenn sich (einmal implementiert) an den Zustandswechsel kaum etwas ändert. Damit werden die Zustände schlanker, da sie sich nicht mehr um die Zustandsübergänge kümmern müssen. Weiterhin werden ihre Abhängigkeiten geringer, da sie das Contextobjekt und andere Zustände nicht mehr kennen müssen. Allerdings ist diese Variante starrer und unflexibler, wenn doch außerordentliche Zustandswechsel notwendig werden.
Folgezustand als Rückgabewert von operate()
Eine besonders interessante Alternative zur Bestimmung der Folgezustände ist folgende: Die operate()-Methode liefert den Folgezustand an den Context zurück. Dadurch kann jeder Zustand seinen Folgezustand selbst bestimmten und der Context ist von dieser Logik entbunden. Dieser Ansatz funktioniert allerdings nur, wenn aus der Programmlogik heraus, die operate()-Methode keinen anderen Rückgabewert haben muss. Ist dies der Fall (z. B. wenn operate() das Ergebnis einer Rechnung zurückliefert), kann dieser Ansatz nicht realisiert werden.
| Erzeugung der Zustandsobjekts bei Bedarf | Erzeugung der Zustandsobjekte im Voraus |
|---|---|
Zustandsobjekte werden erst bei Bedarf instanziiert und nach erfolgter Nutzung sofort wieder gelöscht.
|
Alle möglichen Zustände werden in Voraus instanziiert und nie gelöscht.
|
Wenn die Zustände keinen internen Zustand (also Daten, Membervariablen) haben, so kann zur Einsparung von Ressourcen verschiedenen Contextobjekte die selben Zustandsobjekte nutzen. In diesem Fall könnte man die Realisierung des Singleton Design Pattern für die Zustände in Betracht ziehen.
Das State Design Pattern findet in folgenden Fällen Anwendung:
Objekt mit strukturähnlichen Operationen:
public class ObjectWithoutStateDP {
//Mögliche "Zustände"
private static final int STATE_A = 0;
private static final int STATE_B = 1;
private static final int STATE_C = 2;
//Zahl repräsentiert aktuellen "Zustand"
private int _currentState;
//Methoden ähnlicher Struktur
//Achtung: Strukturierte Denke!
public void go(){
if (_currentState == STATE_A){
//Zustand-A-spezifisches Verhalten...
} else if (_currentState == STATE_B){
//Zustand-B-spezifisches Verhalten...
} else if (_currentState == STATE_C){
//Zustand-C-spezifisches Verhalten...
}
}
public void stop(){
if (_currentState == STATE_A){
//Zustand-A-spezifisches Verhalten...
} else if (_currentState == STATE_B){
//Zustand-B-spezifisches Verhalten...
} else if (_currentState == STATE_C){
//Zustand-C-spezifisches Verhalten...
}
}
public void exit(){
if (_currentState == STATE_A){
//Zustand-A-spezifisches Verhalten...
} else if (_currentState == STATE_B){
//Zustand-B-spezifisches Verhalten...
} else if (_currentState == STATE_C){
//Zustand-C-spezifisches Verhalten...
}
}
}
Nach dem State Design Pattern wird für jedem Zweig der Bedingungsanweisung, die bei einen bestimmten internen Zustand ausgeführt werden, eine eigene Klasse erstellt - die Zustandsklasse. Alles was noch zu tun ist, ist den Methodenaufruf an eine Instanz dieser Zustandsklasse zu delegieren.
Konkrete Anwendungsfälle:

Jedes Werkzeug setzt den Zustand des Editors

Zustandsmaschinen (hier ein DEA/Akzeptor) können mit dem
Zustand Entwurfsmuster abgebildet werden.
| Exkurs: Vergleich von State und Strategy | |
|---|---|
Beim Vergleich des State mit dem Strategy Design Pattern fällt eins sofort auf: beide haben ein identisches Klassendiagramm! Dennoch gibt es große Unterschiede in der Verwendung und der Absicht, die hinter den Pattern stecken, obwohl sie die gleiche Struktur haben. |
|
| State | Strategy |
![]() |
![]() |
Gemeinsamkeit: Beide Pattern kapselt Verhalten in einem separaten Objekt. Der Context delegiert Aufrufe an dieses Objekt. Damit kann das Verhalten des Objekts flexibel (und auch zur Laufzeit) durch Setzen eines anderen Verhaltensobjekts geändert werden. |
|
|
|
| Ergo: Gleiche Struktur, aber unterschiedliche Absicht. | |
Kommentare
das State Pattern zielt auf eine Kapselung des Zustandswechsels ab. Der Client soll sich ja gerade nicht damit beschäftigen müssen, dass die Freundin verschiedenen Zustände hat... schon gar nicht darauf zugreifen (so weit die Definition). Transparenz. Vlt. möchte man das ja auch nicht, weil nur das Subjekt/die Zustände entscheiden sollen, wann der Zustand gewechselt wird. Vlt. führt ja ein verärgern() nicht immer zu einem Zustandswechsel?
Ziel von Patterns sind häufig eine einfache und intuitive Bedienung/API durch den Client:
//Defaultzustand: Neutral
freundin.unterhalten(); //Fününününü.
freundin.verärgern(); //Du spinnst wohl! Ich bin sauer! ;-(
//Ab jetzt: Bockig
Definition State: \"Ermögliche es einem Objekt, sein Verhalten zu ändern, wenn sein interner Zustand sich ändert. Es wird so aussehen, als ob das Objekt seine Klasse gewechselt hat.\"
Das ist das Ziel des State Patterns. Das Zugänglichmachen des Zustandskonzepts für den Client, mag eine Erweiterung sein, die in bestimmten Fällen sinnvoll ist, aber ist auch mit Nachteilen verbunden und entspricht nicht dem klassischen Pattern.
Viele Grüße
Philipp
ich finde die Geschichte richtig gut...einzig ein Detail könnte man (meiner Meinung nach) etwas schöne machen und zwar: Die \"Freundin\" implementiert nicht mehr die Zustände sondern man geht einfach über den getter von Freundin wie folgt:
Freundin f = new Freundin();
f.getZustand().verärgern();
f.getZustand().verärgern();
f.getZustand().unterhalten();
f.getZustand().kussGeben();
f.getZustand().unterhalten();
Das hat zur Folge, dass lediglich ein getter/setter in Freundin existieren muss und somit Freundin von Änderungen in den Zuständen nicht berührt wird.
Gruß
Karl Heinz
Nur eine kleine Anmerkung zu diesem Pattern:
Bitte Kostruktor für Freundin bei \"Freundin mit erweiterter Schnittstelle zum Setzen des Zustands\" einfügen
public Freundin(){
this.setAktuellerZustand(new Neutral(this));
}
Ansonsten richtig gute, verständliche Beispiele!
Viele Grüße aus Bayern
Dann bräuchte der Kontext nur seinen internen Zustand auf diesen Rückgabewert zu setzen und die Zustände müssten nichts vom Kontext wissen.
Seite: 1 -