Windows Azure Cloud Storage ermöglicht es Ihnen bereits ab 0,10€ pro GB/Monat die Vorteile der Cloud zu nutzen.
Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.de  
   
Advanced Developers Conference     
Bücher-Shop mit Amazon (Buchkategorien)C++ : Referenzen zu C++ : C++ Builder : Visual C++ : C# : Java : Spieleprogrammierung : Systemprogrammierung Linux : Software-Entwicklung : .NET : Compilertechnik : Algorithmen & Datenstrukturen : Objektorientierung : Entwurfsmuster : UML : eXtreme Programming : Scrum : Projektmanagement : Software-Testing : Datenbanken : Tom DeMarco : Dilbert : User Friendly
C/C++ Forum :: Die Artikel ::  Das State-Pattern - Des Kaisers neue Kleider     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
Marc++us
Administrator

Benutzerprofil
Anmeldungsdatum: 05.04.2000
Beiträge: 16966
Beitrag Marc++us Administrator 20:53:49 18.05.2009   Titel:   Das State-Pattern - Des Kaisers neue Kleider            Zitieren

Das State-Pattern - des Kaisers neue Kleider

ISBN:9783527700578
Der folgende Text war ursprünglich für mein Buch „Objektorientierte Programmierung für Dummies“ geschrieben worden, und hätte im Buch ab Seite 424 erscheinen sollen. Aus Platzgründen wurde er aber nicht mehr berücksichtigt.

Inhalt

In diesem Artikel erfahren Sie, wie man mit Hilfe des State-Pattern das Innenleben von Objekten ändert, ohne dass man es von außen sieht und als Benutzer oder Besitzer eines Objekts bemerkt. Dadurch wird die Kopplung eines übergeordneten Objekts zu dem benutzten Objekt reduziert.


  • 1 Motivation – des Kaisers neue Kleider
  • 2 Das State-Pattern
  • 3 Das Pimpl-Idiom
  • 4 Zusammenfassung



1 Motivation – des Kaisers neue Kleider
Menschen ändern sich, Objekte auch. Nehmen Sie eine Spielfigur, die in zwei Modi unterwegs sein kann: laufend und schwimmend. Oder kämpfend und bauend. Wer es bodenständiger mag, nimmt einen Server, der an einem Netzwerk hängt. Mal wartet der Server auf Verbindungen, mal besteht eine aktive Verbindung, mal geht der Server in einen Fehlerzustand.
Alle diese Dinge haben eines gemeinsam, in Abhängigkeit seines Zustands ändert ein Objekt sein Verhalten. Nehmen Sie an, dass sich zwei Zustände durch zwei Klassen beschreiben lassen, so könnte man das ja ungefähr so aussehen lassen:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
class State1 : public BasisState;
class State2 : public BasisState;

BasisState* pState = new State1();
pState->doSomething();
delete pState;

pState = new State2();
pState->doSomething();
delete pState;
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
class State1 : public BasisState;
class State2 : public BasisState;

BasisState* pState = new State1();
pState->doSomething();
delete pState;

pState = new State2();
pState->doSomething();
delete pState;
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
class State1 : public BasisState;
class State2 : public BasisState;

BasisState* pState = new State1();
pState->doSomething();
delete pState;

pState = new State2();
pState->doSomething();
delete pState;

Über eine virtuelle Methode kann das Objekt pState sich dann je nach Zustand richtig verhalten. Eine gute Lösung? Leider nein. Bedenken Sie, dass andere Objekte bereits Zeiger und Referenzen auf dieses Objekt besitzen können. Und nun wollen Sie alle bestehenden Zeiger und Referenzen durch neue ersetzen?
Das geht nicht. Das Objekt selbst darf sich für einen Außenstehenden nicht verändern.

2 Das State-Pattern
Ein Entwurfsmuster, das dieses Problem löst, nennt sich State-Pattern. Um ein Objekt seinen inneren Zustand ändern zu lassen, benötigt man insgesamt vier Klassen (für zwei Zustände). Wollte man drei Zustände modellieren, bräuchte man fünf Klassen (für n Zustände immer 2 + n Klassen).



Die Abbildung zeigt den Zusammenhang zwischen den vier Klassen bei der Realisierung des State-Pattern.

Von außen wird nur die Klasse Dummy sichtbar. Intern gibt es für jeden Zustand eine eigene Klasse, diese sind von einer gemeinsamen Basisklasse abgeleitet. Jede Methode von Dummy hat eine entsprechende virtuelle Methode in InnerDummy.
Betrachten Sie ein Programmbeispiel zunächst aus Sicht des Aufrufers.

C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include
"dummy.h"
using namespace std;
int main()
{
   Dummy dummy(5);
   for (int i = 0; i < 1000; i++)
   {
      dummy.doSomething();
      dummy.showValue();
   }
   return 0;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include
"dummy.h"
using namespace std;
int main()
{
Dummy dummy(5);
for (int i = 0; i < 1000; i++)
{
dummy.doSomething();
dummy.showValue();
}
return 0;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include
"dummy.h"
using namespace std;
int main()
{
   Dummy dummy(5);
   for (int i = 0; i < 1000; i++)
   {
      dummy.doSomething();
      dummy.showValue();
   }
   return 0;
}

Wie Sie sehen können, erfährt man als Nutzer der Klasse Dummy nichts von deren Eigen- und Innenleben. Das ist schon mal ein guter Beginn. Nun geht es weiter zu den inneren Klassen.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef DUMMY_H
#define
DUMMY_H
class Dummy;
class InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy) = 0;
};
class InnerDummyA : public InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy);
};
class InnerDummyB : public InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy);
};
class Dummy
{
public:
   Dummy(int value);
   ~Dummy();
   void showValue()
   {
      pDummy->showValue(this);
   }
   void doSomething();
   int getValue() const
   {
      return value;
   }
private:
   InnerDummy* pDummy;
   int value;
};
#endif
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef DUMMY_H
#define
DUMMY_H
class Dummy;
class InnerDummy
{
public:
virtual void showValue(Dummy* pDummy) = 0;
};
class InnerDummyA : public InnerDummy
{
public:
virtual void showValue(Dummy* pDummy);
};
class InnerDummyB : public InnerDummy
{
public:
virtual void showValue(Dummy* pDummy);
};
class Dummy
{
public:
Dummy(int value);
~Dummy();
void showValue()
{
pDummy->showValue(this);
}
void doSomething();
int getValue() const
{
return value;
}
private:
InnerDummy* pDummy;
int value;
};
#endif
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef DUMMY_H
#define
DUMMY_H
class Dummy;
class InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy) = 0;
};
class InnerDummyA : public InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy);
};
class InnerDummyB : public InnerDummy
{
public:
   virtual void showValue(Dummy* pDummy);
};
class Dummy
{
public:
   Dummy(int value);
   ~Dummy();
   void showValue()
   {
      pDummy->showValue(this);
   }
   void doSomething();
   int getValue() const
   {
      return value;
   }
private:
   InnerDummy* pDummy;
   int value;
};
#endif

Beachten Sie, dass es für die Methode Dummy::showValue auch noch virtuelle Methoden in InnerDummy (und natürlich auch den abgeleiteten Klassen) gibt. Zudem besitzt Dummy einen Zeiger auf InnerDummy – wozu sehen Sie, sobald Sie sich die mplementation betrachten.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include
"dummy.h"
using namespace std;
void InnerDummyA::showValue(Dummy* pDummy)
{
   cout << "Jetzt bin ich A! "
        << pDummy->getValue() <<endl;
}
void InnerDummyB::showValue(Dummy* pDummy)
{
   cout << "Jetzt bin ich B! "
        << pDummy->getValue() << endl;
}
Dummy::Dummy(int value)
   : value(value)
{
   pDummy = new InnerDummyA();
}
Dummy::~Dummy()
{
   delete pDummy;
}
void Dummy::doSomething()
{
   InnerDummy* new_dummy;
   if (rand() % 2)
   {
      new_dummy = new InnerDummyA();
   }
   else
   {
      new_dummy = new InnerDummyB();
   }
   delete pDummy;
   pDummy = new_dummy;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include
"dummy.h"
using namespace std;
void InnerDummyA::showValue(Dummy* pDummy)
{
cout << "Jetzt bin ich A! "
<< pDummy->getValue() <<endl;
}
void InnerDummyB::showValue(Dummy* pDummy)
{
cout << "Jetzt bin ich B! "
<< pDummy->getValue() << endl;
}
Dummy::Dummy(int value)
: value(value)
{
pDummy = new InnerDummyA();
}
Dummy::~Dummy()
{
delete pDummy;
}
void Dummy::doSomething()
{
InnerDummy* new_dummy;
if (rand() % 2)
{
new_dummy = new InnerDummyA();
}
else
{
new_dummy = new InnerDummyB();
}
delete pDummy;
pDummy = new_dummy;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include
"dummy.h"
using namespace std;
void InnerDummyA::showValue(Dummy* pDummy)
{
   cout << "Jetzt bin ich A! "
        << pDummy->getValue() <<endl;
}
void InnerDummyB::showValue(Dummy* pDummy)
{
   cout << "Jetzt bin ich B! "
        << pDummy->getValue() << endl;
}
Dummy::Dummy(int value)
   : value(value)
{
   pDummy = new InnerDummyA();
}
Dummy::~Dummy()
{
   delete pDummy;
}
void Dummy::doSomething()
{
   InnerDummy* new_dummy;
   if (rand() % 2)
   {
      new_dummy = new InnerDummyA();
   }
   else
   {
      new_dummy = new InnerDummyB();
   }
   delete pDummy;
   pDummy = new_dummy;
}

Jede der show-Methoden wird in InnerDummyA und InnerDummyB anders implementiert, das ist auch logisch, schließlich ändert sich mit dem Zustand das Verhalten. Im Konstruktor von Dummy wird zunächst ein InnerDummyA-Objekt erzeugt, das Objekt befindet sich zu Beginn im Zustand »A«. Ruft man von außen Dummy::showValue auf, so wird in Wirklichkeit die virtuelle Methode des zurzeit eingehängten InnerDummy-Objekts aufgerufen. Je nach Zustand des Objekts Dummy also eine andere Methode.
In der Funktion doSomething wird ein Zustandswechsel simuliert, hier einfach durch die Auswertung einer Zufallszahl. Je nach Ergebnis wechselt das Dummy-Objekt mal in den Zustand »A«, mal in den Zustand »B«. Sie sehen an der Bildschirmausgabe, dass der Aufrufer immer showValue aufruft, die Ausgabe aber dennoch unterschiedlich sein kann.

3 Das Pimpl-Idiom

Rein technisch sieht das State-Pattern einem anderen Trick sehr ähnlich, dem so genannten Pimpl-Idiom. Dort besitzt eine äußere Klasse nur einen Zeiger auf eine innere Klasse und sonst nichts, ganz ähnlich zum State-Pattern:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Inner;
class Outer
{
public:
   Outer()
   {
      pImpl = new Inner();
   }
   ~Outer()
   {
      delete pImpl;
   }
   void doSomething()
   {
      pImpl->doSomething();
   }
private:
   Inner* pImpl;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Inner;
class Outer
{
public:
Outer()
{
pImpl = new Inner();
}
~Outer()
{
delete pImpl;
}
void doSomething()
{
pImpl->doSomething();
}
private:
Inner* pImpl;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Inner;
class Outer
{
public:
   Outer()
   {
      pImpl = new Inner();
   }
   ~Outer()
   {
      delete pImpl;
   }
   void doSomething()
   {
      pImpl->doSomething();
   }
private:
   Inner* pImpl;
};

Auch hier ist es so, dass die Klasse Inner genau die gleichen Methoden besitzt wie die äußere Klasse Outer, aber nicht, um Zustände zu modellieren. Der Gag an Pimpl ist, dass man die komplette Klasse Inner in einer anderen Datei implementieren kann. Hat Sie nicht auch schon oft gestört, dass bei der Ergänzung einer privaten Variablen in einer Klasse plötzlich alles neu kompiliert werden muss? Bei Pimpl wird dies vermieden, da sämtliche privaten Variablen und Methoden nur in Inner realisiert werden. Die Klasse Outer ändert sich dadurch nicht und man kann Inner verändern, ohne das komplette Projekt neu kompilieren zu müssen, es reicht aus, Inner neu zu übersetzen. Pimpl nennt man deshalb auch Compiler-Firewall.

Der Begriff Pimpl kommt von pointer to implementation und erinnert daran, dass ein Zeiger auf das tatsächliche Implementations-Objekt benutzt wird, um die Klasse zu realisieren. Das Pimpl-Idiom wird oft in größeren Open-Source-Projekten verwendet, in denen viele Leute viele verschiedene Dateien bearbeiten.

4 Zusammenfassung

Das State-Pattern ermöglicht es einem Objekt je nach innerem Zustand ein anderes Verhalten zu zeigen.

Implementiert wird das State-Pattern dadurch, dass man für jeden Zustand eine eigene Klasse realisiert. Diese besitzen eine gemeinsame Basisklasse. Für jede Funktion, deren Verhalten sich ändern soll, wird in der Basisklasse eine virtuelle Methode angelegt, die in den abgeleiteten Klassen überschrieben wird. Ruft man von außen eine Methode des Objekts auf, wird in Wirklichkeit eine Methode des gerade aktiven inneren Zustand-Objekts aufgerufen.

Interessant ist das State-Pattern auch, falls jeder Zustand des Objekts noch eigene Variablen benötigt. Packt man alles in eine einzige Klasse, so landen letztlich viele Variablen in einer einzigen Klasse, die aber je nach Zustand immer nur einen Teil benötigt. Bei der State-Lösung ist jede Zustand-Klasse so schlank wie möglich.

_________________
Viele Grüße
Marc++us
C++.de


Zuletzt bearbeitet von Marc++us am 18:24:03 16.05.2010, insgesamt 3-mal bearbeitet
_christoph_
Unregistrierter




Beitrag _christoph_ Unregistrierter 14:26:16 26.06.2009   Titel:              Zitieren

Hallo Marc++us,

da ich das Pattern gerade benötige, kam dein Artikel genau zur rechten Zeit ;)
Habe allerdings noch ne Ergänzung:
So wie du das State-Pattern beschrieben hast, könnte man es leicht mit dem Strategy-Pattern verwechseln.
Der Unterschied ist aber, dass die State-Objekte normalerweise selbst darauf Einfluss haben, welches das "Folge-Objekt" des States ist.
Die Dummy-Klasse müsste beispielsweise noch eine Methode wie setNextState(InnerDummy* next) haben. Darüber hinaus sind State-Objekt häufig als Singletons implementiert. Die Literatur beschreibt die Implementierung der Transitions aber leider auch nicht so genau.

Hier ist das nochmal recht gut beschrieben:
http://sourcemaking.com/design_patterns/state
chashprogramist
Mitglied

Benutzerprofil
Anmeldungsdatum: 10.07.2009
Beiträge: 1
Beitrag chashprogramist Mitglied 09:33:05 10.07.2009   Titel:              Zitieren

Die Template-Klasse ist ein ausgezeichnetes Beispiel dafür, Staatsministerin für Wissenschaft! Vielen Dank für den Artikel und, wenn Sie kann für mehrere dieser!

Standard Zustand hatten wir in der Klasse von der Analyse und Gestaltung von IT-Systemen, aber dank Ihren Artikel habe ich verstanden, es besser!

_________________
Pozdrowienia z Krakowa!
AlGaN
Mitglied

Benutzerprofil
Anmeldungsdatum: 04.08.2005
Beiträge: 26
Beitrag AlGaN Mitglied 10:21:01 27.07.2009   Titel:   Beispielcode Buch            Zitieren

Hallo Marc++us,

sorry für eine mgl.weise dumme Frage: Ich hab mir dein Buch "Objektorientierte Programmierung für Dummies" gekauft, da das Buch nicht mehr gedruckt wird, gibt es auf der Seite des Verlags keinen Link mehr für den Beispielcode. Deshalb meine Frage: Wo kann man sich die Beispiele vom Buch noch downloaden?

Danke,
AlGaN
pumuckl
Moderator

Benutzerprofil
Anmeldungsdatum: 21.06.2005
Beiträge: 6320
Beitrag pumuckl Moderator 11:06:59 27.07.2009   Titel:   Re: Beispielcode Buch            Zitieren

AlGaN schrieb:
Hallo Marc++us,

sorry für eine mgl.weise dumme Frage: Ich hab mir dein Buch "Objektorientierte Programmierung für Dummies" gekauft, da das Buch nicht mehr gedruckt wird, gibt es auf der Seite des Verlags keinen Link mehr für den Beispielcode. Deshalb meine Frage: Wo kann man sich die Beispiele vom Buch noch downloaden?

Danke,
AlGaN


Ich bezweifle das Marcus hier allzuoft reinschaut. Bessere chancen hast du vermutlich wenn du ihn direkt anschreibst über die Mailing-Funktion hier im Forum.

_________________
Du brauchst Hilfe? - Kleines Einmaleins der Forenregeln.
When your hammer is C++, everything begins to look like a thumb. (Steve Haflich)
Tyrdal
Mitglied

Benutzerprofil
Anmeldungsdatum: 20.09.2007
Beiträge: 440
Beitrag Tyrdal Mitglied 13:12:52 07.08.2009   Titel:              Zitieren

war Unsinn


Zuletzt bearbeitet von Tyrdal am 13:13:46 07.08.2009, insgesamt 1-mal bearbeitet
K4L
Unregistrierter




Beitrag K4L Unregistrierter 14:20:34 07.09.2009   Titel:              Zitieren

hm guter artikel eigtl...was ich aber schade und etwas komisch finde, dass du den klassen nicht-sagende namen wie dummy gegeben hast...
Erhard Henkes
Mitglied

Benutzerprofil
Anmeldungsdatum: 25.04.2000
Beiträge: 11885
Beitrag Erhard Henkes Mitglied 22:46:03 15.10.2009   Titel:              Zitieren

Ich habe das State Pattern Design beim Hobby-Roboter Nibo auf einem ATmega128 eingesetzt: http://www.henkessoft.de/Roboter/Nibo.htm#mozTocId872713

_________________
OS-Development-, C++, Win32-API-, MFC-, Chemie-, Robotik- und Flugsimulator-Tutorials
http://www.henkessoft.de/index.htm
C/C++ Forum :: Die Artikel ::  Das State-Pattern - Des Kaisers neue Kleider   Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können keine Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info, www.c-sar.de, www.c-plusplus.net und www.baeckmann.de enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.