Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.de  
   

Die mobilen Seiten von c++.de:
http://m.c-plusplus.de
Infos hier [BETA]

  
c++.de :: Die Artikel ::  Einführung in Design Patterns     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 09:51:31 05.08.2006   Titel:   Einführung in Design Patterns            Zitieren

Einführung in Design Patterns

Inhaltsverzeichnis
  1. Vorwort
  2. Einleitung
  3. Was sind Design Patterns ?
  4. Ausgesuchte Design Patterns erklärt
    4.1 Das Adapter Pattern
    4.2 Das Singleton Pattern
    4.3 Das Observer Pattern
    4.4 Das Strategy Pattern
  5. Zusammenfassung
  6. Literatur



1 Vorwort

Dieser Artikel wendet sich hauptsächlich an die Leute, welche noch nie bzw. so gut wie nie mit Design Patterns (deutsch: Entwurfsmustern) zu tun hatten. Wer schon mit Design Patterns gearbeitet hat, wird hier wohl nichts Neues finden. Der Artikel richtet sich also vornehmlich an Anfänger. Allerdings sollten gewisse Grundkenntnisse gegeben sein, die da wären:
  • Grundlagenkenntnisse in C++
  • Basiswissen über OOP-Techniken wie z.B. abstrakte Klassen und Polymorphie

Das war es dann auch schon, was benötigt wird. Ich werde bei den gegebenen Beispielen zwar noch kurz etwas zur Polymorphie erwähnen. Wer allerdings noch nie etwas von diesen Begriffen gehört hat, sollte sich erst einmal darüber informieren.

Noch etwas zum Schluss:
Zu Design Patterns gibt es zahlreiche Bücher und Tutorials, die sich ausschließlich mit diesem Thema befassen. Ein einzelner Artikel kann und soll auch dieses große Gebiet nicht komplett abdecken. Ziel dieses Artikels ist es, den Leser in das Thema einzuführen und vielleicht auch die Lust nach mehr zu wecken. Daher sei hier schon mal auf die Literaturliste am Ende des Artikels verwiesen.

Die gezeigten Implementierungen sind keineswegs optimal und auch der C++-Code ist nicht ideal. Es ging mir darum, die grundlegenden Prinzipien so einfach wie möglich darzustellen - ohne großes Drumherum und spezifischeren C++ Code. Auch die gezeigten UML-Diagramme sind so einfach wie möglich gehalten und deshalb nicht immer zu 100% konform zur UML-Spezifikation.


2 Einleitung

Vor der Ära der objektorientierten Programmierung wurden Programme fast ausschließlich prozedural entwickelt. Eine herausragende Eigenschaft, die durch die Einführung des objektorientierten Ansatzes geschaffen wurde, war die, dass komplexer Programmcode nun viel besser und übersichtlicher gegliedert werden konnte. Die Komplexität der zu entwickelnden Programme stieg jedoch an und es mussten neue Techniken her, um im Code die Übersicht zu behalten. Ein Beispiel dafür ist die STL (Standard Template Library). Diese soll den Programmierer entlasten, indem sie ihm Komponenten für sich ständig wiederholende Aufgaben, wie z.B. die Verwaltung von Daten in Listen, abnimmt. Dadurch kann sich der Entwickler auf die tatsächliche Funktionalität seiner Anwendung konzentrieren.
Ähnlich zu diesem Ansatz hat sich in den letzten Jahren eine weitere Technik etabliert, die es einem erlaubt, vordefinierte und bewährte Muster zu verwenden. Jedoch ist der Scope ein ganz anderer. Man kann komplette Programmteile mit diesen Mustern substituieren, was dem Programmierer eine enorme Zeitersparnis und eine geringere Fehleranfälligkeit einbringt. Diese Muster nennt man Design Patterns oder auch Entwurfsmuster.


3 Was sind Design Patterns ?

Kurz und bündig: Design Patterns sind bewährte Lösungen zu bekannten, häufiger auftretenden Problemen in der Softwareentwicklung.
In der Vergangenheit kristallisierten sich einige Probleme heraus, die häufig und vor allem auch in verschiedenen Zusammenhängen auftraten. Zu diesen Problemen wurden viele Lösungen entwickelt; es wurden aber nur die besten Lösungen angenommen. Eine solche bewährte Lösung ist ein Design Pattern. Ein Entwurfsmuster ist immer kontextunabhängig, d. h., man kann ein und dasselbe Design Pattern z. B. sowohl in einem Computerspiel als auch in einer Tabellenkalkulationsapplikation verwenden.
Hier zur Motivation ein paar Vorteile von Design Patterns:
  • Zeitersparnis: Durch die Wiederverwendung von bewährten Mustern spart man enorm viel Zeit, da man das Rad nicht jedes Mal neu erfinden muss
  • Fehlerfreiheit: Man kann sich sicher sein, dass ein Design Pattern frei von Fehlern ist
  • Gemeinsame Kommunikationsgrundlage: Auch andere Entwickler kennen Design Patterns, was zu einem gemeinsamen Verständnis und zu einer besseren Kommunikation, insbesondere in größeren Projekten, führt
  • Sauberes OO-Design: Durch das Erlernen von Design Patterns wird man mit der Zeit auch ein besseres Verständnis für objektorientierte Designs erlangen


4 Ausgesuchte Design Patterns erklärt

4.1 Das Adapter Pattern
Bei dem ersten Pattern, das wir betrachten wollen, handelt es sich um das Adapter Pattern. Dieses Muster ist weit verbreitet und es kann gut sein, dass einige Leser es schon angewendet haben, ohne dies genau zu wissen.

Es kommt oft vor, dass ein Client (z. B. eine Klasse) auf eine andere Klasse zugreift und von dieser Klasse eine bestimmte Schnittstelle nach außen hin erwartet. Jetzt kann es aber vorkommen, dass diese Klasse zwar die vom Client benötigte Funktionalität anbietet, aber nicht die erwartete Schnittstelle besitzt, sondern eine andere. Das ist der Punkt, in dem das Adapter Pattern ins Spiel kommt. Das Adapter Pattern erlaubt es, verschiedenen Klassen trotz "inkompatibler" Schnittstellen zusammenzuarbeiten. Ein vielleicht geläufigerer Begriff für dieses Entwurfsmuster ist der des Wrappers. Am einfachsten lässt sich dies an einem Beispiel nachvollziehen.

Nehmen wir an, dass wir in einer Firma an einem Projekt arbeiten und die Funktionalität benötigen, verschiedene geometrische Figuren zu zeichnen (ja ja, sehr realitätsbezogen, ich weiß). Wie gehen wir nun vor? Als brave Entwickler definieren wir erst einmal eine abstrakte Basisklasse Shape und von dieser leiten wir dann die konkreten Klassen ab. Das sähe dann so aus (UML):



Auch ohne UML-Kenntnisse sollte man dieses einfache Diagramm verstehen. In unserem Programm werden wir ausschließlich mit dem von Shape bereitgestellten Interface (display, scale, setColor) arbeiten und trotzdem die konkreten Methoden von den jeweiligen Objekten (Rectangle, Line) aufrufen können - Polymorphie macht es möglich. Wie das funktioniert, sollte dem Leser klar sein.
Ein möglicher Programmausschnitt könnte folgendermaßen aussehen:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
list<Shape*> shapes;
 
Shape* rect = new Rectangle();
Shape* line = new Line();
...
shapes.push_back(rect);
shapes.push_back(line);
...
//alle gemoetrischen Figuren anzeigen
list<Shape*>::iterator iter = shapes.begin();
for ( ; iter != shapes.end; shapes++ )
    (*iter)->display();
....



Nun wollen wir als weitere Anforderung auch Kreise in unserem Programm zeichnen können. Glücklicherweise stellt sich heraus, dass schon einmal jemand in der Firma eine Kreis-Klasse geschrieben hat, die uns auf jeden Fall die Funktionalität bietet, die wir benötigen. Wir brauchen also keine neue Kreis-Klasse implementieren. Jedoch sieht die bereits vorhandene Kreis-Klasse so aus:



Die benötigte Funktionalität haben wir also. Da wir aber das bisherige polymorphe Verhalten beibehalten wollen, stehen wir vor folgenden Problemen:
  • Unterschiedliche Namen und Parameter: Die Methodennamen variieren mit denen unserer Schnittstelle von Shape. Außerdem gibt es unterschiedliche Parameter (-> scale)
  • Vererbung: Diese Klasse ist nicht von unserer abstrakten Basis-Klasse Shape abgeleitet. Damit wäre unser polymorphes Verhalten zunichtegemacht.

Natürlich könnten wir jetzt einfach die breits implementierte Kreis-Klasse umändern, so dass sie in unser Design passt. Das wäre aber ziemlich unschön und fehleranfällig; zudem sollte nur der Autor selbst seine Klassen ändern. Stattdessen wenden wir das Adapter-Pattern an:
Wir erstellen eine neue Klasse namens Circle und lassen diese von unserer abstrakten Basisklasse Shape erben. Die Klasse Circle hat ein Objekt der Klasse AlreadyImplementedCircle als Membervariable. Methodenaufrufe von Circle leiten wir weiter an die Methoden von AlreadyImplementedCircle:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
class Circle : public Shape
{
private:
   AlreadyImplementedCircle* c;
public:
   Circle() { c = new AlreadyImplementedCircle(); }
   void display() {  c->showCircle(); }
   void setColor(Color color) { c->changeColor(color); }
   void scale(float factor) { c->scale(factor,factor); }
 
   ~Circle() { delete c; }
};


So können wir einerseits die Funktionalität von AlreadyImplementedCircle nutzen und behalten aber andererseits unsere Vererbungsstruktur mit ihrem polymorphen Verhalten bei.

Das Adapter-Pattern ist trotz seiner Einfachheit ein sehr mächtiges Pattern und kann konsequent angewandt zu flexiblen Klassendesigns führen. Vererbung ist zwar eine sehr mächtige Technik, gleichzeitig aber wohl auch eine der am gefährlichsten. Es ist oft besser eine Klasse durch die Anwendung des Adapter-Patterns in eine Vererbungslinie zu bringen, anstatt die Klasse direkt erben zu lassen



4.2 Das Singleton Pattern

Da das Singleton Pattern relativ häufig in diversen Foren und Büchern genannt wird, wird an dieser Stelle kurz auf das Pattern eingegangen.
Das Prinzip, das dahinter steht, ist eigentlich relativ einfach: Man will erreichen, dass es maximal eine Instanz einer Klasse gibt und dass man auf diese von überall her einfach zugreifen kann. Das sieht dann z. B. so aus:

C++:
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
// singleton.h
class Singleton
{
 
public:
    static Singleton& Instance()
    {
        //das einzige Objekt dieser Klasse erzeugen und als Referenz zurückgeben
        static Singleton instance;
        return instance;
    }
 
    void doSomething() { }
 
 
protected:
    Singleton() { }
 
    //Copy-Konstruktor: Hierdurch werden Kopien dieses Objektes verhindert (da protected)
    Singleton(const Singleton& other) { }
   
};
 
 
//so kann man komfortabler auf das Singleton zugreifen
inline Singleton& getSingletonInstance() { return Singleton::Instance(); }


Ein paar Dinge, die einem hier auffallen sollten:
  • Es ist ein Standard-Konstruktor definiert und dieser ist protected, d. h., man wird diese Klasse weder durch Singleton a; noch durch Singleton* a = new Singleton(); instanziieren können. Zudem wurde explizit ein leere Copy-Konstruktor definiert, welcher ebenfalls protected ist, so dass man auch keine Kopien dieses Objektes anlegen kann
  • Die Methode "Instance" gibt eine Referenz auf ein Singleton-Objekt zurück. Über diese Methode kommen wir also an unser Singleton-Objekt. Und da die Methode statisch deklariert ist, gehört sie zur Klasse selbst und kann somit auch über den Klassenbezeichner aufgerufen werden
  • Wie man sieht, wird in der Methode "Instance" ein neues Objekt ("instance") dieser Klasse erzeugt. Normalerweise würde dieses Objekt nach dem Verlassen dieser Methode automatisch vom Stack gelöscht werden. Da es aber mit static erzeugt wurde, überlebt dieses Objekt die Zeitspanne des Methodenaufrufs. Genauso wichtig ist es zu wissen, dass dieses Objekt genau einmal erzeugt wird, nämlich beim ersten Methodenaufruf. Bei nachfolgenden Methodenaufrufen wird das Objekt nicht jedes Mal neu erzeugt. Es handelt sich hier also immer um dasselbe eine Objekt, welches dann an den Aufrufer zurückgegeben wird.

Benutzen könnte man diese Klasse dann z. B. so:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "singleton.h" // Die Singleton-Klasse von oben
 
using namespace std;
 
int main()
{
    // Hier wird tatsächlich das Singleton-Objekt in der Instance-Methode instanziiert und zurückgegeben
    Singleton::Instance().doSomething();       
 
    // Hier wird nun einfach das bereits weiter oben instanziierte Objekt zurückgegeben
    getSingletonInstance().doSomething();
 
    // Adressen des Objektes ausgeben: Diese sind immer gleich, d. h., es handelt sich immer um dasselbe Objekt
    cout << hex << &getSingletonInstance() << endl;
    cout << hex << &getSingletonInstance() << endl;
 
    return 0;
}



Welchen Nutzen haben Singletons eigentlich? Es kann vorkommen, dass man globale Objekte benötigt, die überall in jeder anderen Klasse sichtbar sind. Um dies zu verwirklichen, gibt es mehrere Möglichkeiten. Eine Möglichkeit wäre, das gewünschte Objekt zu instanziieren und es dann jeder Methode, die es benötigt, als Parameter zu übergeben. Das wäre aber relativ ineffizient und würde auch nicht unbedingt die Lesbarkeit des Codes erhöhen. Eine weitere Möglichkeit wäre, ein Objekt in einer Quellcode-Datei zu instanziieren und anschließend in den anderen Dateien mithilfe des "extern"-Schlüsselworts darauf zuzugreifen. Aber auch das ist eine unelegante Lösung. Das Singleton Pattern bietet eben genau hierfür die Lösung. Jedoch sollte man sehr vorsichtig mit diesem Pattern umgehen, denn es kann schnell dazu verleiten, die ein oder andere Klasse leichtfertig als Singleton zu definieren (globale Dinge verführen immer ;)), was dann wiederum zu sehr inflexiblen Designs führen kann. Durch seine statische Natur hat das Singleton einige Unzulänglichkeiten:
  • Es gibt immer nur eine Instanz. Was aber wenn plötzlich Anforderungen kommen, wonach man verschiedene Zustände in verschiedenen Instanzen unterscheiden muss? Man müsste sein ganzes Design, das bisher auf das Singleton-Pattern fixiert war, umändern
  • Was passiert wenn sich mehrere Singletons gegenseitig referenzieren müssen? Dies zu lösen ist nicht gerade trivial
  • In puncto Vererbung ist man auch sehr eingeschränkt. Man kann eine Singleton-Klasse zwar vererben, jedoch wird man z. B. kein polymorphes Verhalten erreichen können (statische Methoden können nicht virtuell sein)
  • Beim Multi-Threading können ebenfalls Probleme auftreten, was hier aber nicht näher erläutert werden soll, da es sich auch nicht unbedingt um ein singletonspezifisches Problem, sondern um ein allgemeineres Synchronisationsproblem handelt. Dennoch ist es wichtig, dies zu wissen


Man sollte es sich also *sehr* gründlich überlegen, bevor man sich für eine Singleton-Variante einer Klasse entscheidet. Es gibt jedoch sinnvolle Fälle für Singletons. Oft wird eine Klasse als Singleton realisiert, wenn es darum geht, bestimmte vorhandene (Hardware-)Ressourcen zu modellieren. Man könnte z. B. den direkten Zugriff auf die Grafikkarte als Singleton modellieren. Für ein Computerspiel wäre dies durchaus sinnvoll, da man den Zugriff auf die Grafikkarte an sehr vielen Stellen benötigt und es ja auch genau eine Grafikkarte gibt.

Abschließend sei noch angemerkt, dass hier nur eine mögliche (die einfachste) von mehreren möglichen Singleton-Implementierungen gezeigt wurde. Viele Implementierungen benutzen auch Pointer als Member-Variablen, um das Singleton-Verhalten zu erreichen. Damit sind auch weitaus flexiblere Implementierungen möglich, sofern sie denn gebraucht werden.
In Alexandrescus Buch [3] wird auf die oben genannten Unzulänglichkeiten eingegangen und mögliche Lösungen aufgezeigt.




4.3 Das Observer Pattern

Das Observer Pattern ist vom Prinzip her relativ leicht zu verstehen, jedoch gibt es auch hier verschiedene Implementierungen, die unterschiedliche spezielle Probleme adressieren. In diesem Artikel wird nur eine einfache Implementierung gezeigt, ohne auf Besonderheiten einzugehen.
Worum geht es beim Observer Pattern? Jedes Objekt hat einen Zustand, in dem es sich aktuell befindet. Bei Änderungen an diesem Zustand kann es vorkommen, dass es andere Objekte gibt, die von diesem einen Objekt abhängig sind und von solchen Zustandsänderungen benachrichtigt werden müssen. Man bezeichnet diese abhängigen Objekte als Observer und das zu beobachtende Objekt als Subject.

Ein prominentes Beispiel hierfür ist das MVC-Prinzip (Model-View-Controller). Dabei will man die GUI (den View) von den Daten (dem Model) trennen, wodurch eine hohe Flexibilität entsteht. Dadurch kann man z. B. zu ein und denselben Daten (= Model = Subject) verschiedene Ansichten(= View = Observer) haben. Sobald sich etwas am Model ändert, benachrichtigt dieses die Observer (also die Ansichten), woraufhin diese ihre GUI-Komponenten aktualisieren.
Der Controller hält dabei sowohl das Model als auch die Views und meistens auch zusätzliche GUI-Komponenten, um Eingaben entgegenzunehmen, aber das spielt jetzt für uns und das Observer Pattern keine Rolle.
Ein anderes Beispiel ist das aus Java wohlbekannte Event/Listener-Modell.

Das gewünschte Verhalten des Observer Pattern kann man folgendermaßen erreichen:
  • Man kann Observer bei einem Subject "anmelden"
  • Jeder Observer hat eine update-Methode, in der der eigene Zustand aktualisiert wird. Das bedeutet auch, dass man den Zustand des zu beobachtenden Subjekts braucht, um den eigenen Zustand mit diesem zu synchronisieren
  • Ändert sich der Zustand eines Subjekts werden die Observer benachrichtigt (englisch: to notify), indem deren update-Methode aufgerufen wird

Klingt kompliziert? Ist es aber eigentlich nicht. Am besten sieht man dies anhand eines Code-Beispiels.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Subject.h //
#include <list>
#include "ObserverInterface.h"
 
using namespace std;
 
class Subject
{
 
public:
    void attach(ObserverInterface* observer);
    void detach(ObserverInterface* observer);
    void notify();
 
private:
    list<ObserverInterface*> observers;
 
 
protected:
    // Durch protected-Konstruktor wird diese Klasse abstrakt
    Subject() {};
 
};


Mit der abstrakten Basisklasse Subject vereinbaren wir eine gemeinsame Schnittstelle für unsere späteren konkreten Subjekte.
Mit attach kann man einen Observer hinzufügen, mit detach kann man einen Observer wieder entfernen. Essenziell ist hier die notify-Methode. Diese ist dafür zuständig, unsere Observer zu benachrichtigen. Um unsere registrierten Observer zu verwalten, packen wir sie in eine STL-Liste. Wichtig ist hierbei zu beachten, dass wir nur Zeiger auf ObserverInterfaces abspeichern. Dies erlaubt uns später die Methoden von konkreten Observern polymorph aufzurufen.

Wie sieht jetzt ein ObserverInterface aus? Nun, ganz einfach: Alles, was wir benötigen, ist eine update-Methode:
C++:
// ObserverInterface.h //
class ObserverInterface
{
public:
    virtual void update() = 0;
};


Als Nächstes betrachten wir die Implementierung von Subject:
C++:
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
// SubjectImpl.cpp //
#include "Subject.h"
#include "ObserverInterface.h"
 
void Subject::attach(ObserverInterface* observer)
{
    observers.push_back(observer);
}
 
 
void Subject::detach(ObserverInterface *observer)
{
    observers.remove(observer);
}
 
 
void Subject::notify()
{
    list<ObserverInterface*>::iterator iter = observers.begin();
    for ( ; iter != observers.end(); iter++ )
    {
        (*iter)->update();
       
    }      
}


Wichtig ist hier, wie schon gesagt, die notify-Methode. Hier wird die ganze Liste an Observern durchgegangen und von jedem einzelnen die update-Methode aufgerufen.

Was wir jetzt brauchen, ist ein konkretes Subject, welches tatsächliche Daten repräsentiert:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // ConcreteSubject.h //
#include <string>
#include "Subject.h"
 
using namespace std;
 
class ConcrecteSubject : public Subject
{
 
private:
    string data;
 
public:
    void setData(string _data) { data = _data; }
    string getData() { return data; }
    ConcreteSubject() : Subject() {}
};


Gut, extrem simpel, aber für unsere Zwecke ausreichend.
Man hätte in diesem Beispiel jetzt natürlich auch auf die Vererbungslinie von Subject und ConcreteSubject verzichten und stattdessen die Methoden aus Subject in ConcreteSubject reinpacken können. Aber durch diese Vererbung hat man eine schöne Trennung für den Code, der das Observer Pattern betrifft, und für den Code, der die eigentlichen (Anwendungs-)Daten dieser Klasse betrifft.

Nun definieren wir einen konkreten Observer:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 // ConcreteObserver.h //
#include <string>
#include "ObserverInterface.h"
#include "ConcreteSubject.h"
 
using namespace std;
class ConcreteObserver : public ObserverInterface
{
 
private:
    string name;
    string observerState;
    ConcreteSubject* subject; // Dieses Objekt hält die Daten (=notifier)
 
public:
    void update();
    void setSubject(ConcreteSubject* subj);
    ConcreteSubject* getSubject();
    ConcreteObserver(ConcreteSubject* subj, string name);
 
};


Um einen Observer zu identifizieren, verpassen wir ihm einen Namen. Die observerState-Variable ist dafür da, um den Zustand des Observers mit dem Zustand des Subjekts konsistent zu halten. Wichtig ist hierbei, dass dem Observer-Konstruktor auch gleichzeitig das zu beobachtende Subjekt mit übergeben werden muss. Nun zur Implementierung:

C++:
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
 // ConcreteObserverImpl.cpp //
#include <iostream>
#include "ConcreteObserver.h"
 
using namespace std;
 
 
// Daten anzeigen
void ConcreteObserver::update()
{
    observerState = subject->getData();
    cout << "Observer " << name << " hat neuen Zustand: " << observerState << endl;
}
 
void ConcreteObserver::setSubject(ConcreteSubject* obj)
{
    subject = obj;
}
 
ConcreteSubject* ConcreteObserver::getSubject()
{
    return subject;
}
 
 
ConcreteObserver::ConcreteObserver(ConcreteSubject* subj, string n)
{
    name = n;
    subject = subj;
}


In der update-Methode holen wir den aktuellen Status des Subjekts und geben ihn aus.
Zusammenfassend lässt sich hier sagen: Wir haben eine Schnittstelle für Subjekte, welche es uns erlaubt, Observer hinzuzufügen und zu entfernen. Parallel haben wir eine Schnittstelle für Observer, welche es uns erlaubt, für jeden konkreten Observer die update-Methode aufzurufen. Zudem haben wir konkrete Observer und Subjekte definiert.

Hier ein Beispiel für die Benutzung der erstellten Klassen:
C++:
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
 // Main.cpp //
#include "ObserverInterface.h"
#include "ConcreteSubject.h"
#include "ConcreteObserver.h"
 
 
int main()
{
 
    // Das Objekt hält alle Daten (=notfier = subject)
    ConcreteSubject* subj = new ConcretSubject();
   
    ObserverInterface* obs1 = new ConcreteObserver(subj,"A");
    ObserverInterface* obs2 = new ConcreteObserver(subj,"B");
 
    // Observer(=views) an Subjekt anhängen (attachen)
    subj->attach(obs1);
    subj->attach(obs2);
 
    // Daten ändern und Observer informieren (notify)
    subj->setData("TestData");
    subj->notify();
 
       /*
        Ausgabe:
        Observer A hat neuen Zustand: TestData
        Observer B hat neuen Zustand: TestData
       */

 
    return 0;
 
}


Es ist nun ein Leichtes, ohne Änderung des bestehenden Codes weitere Observer hinzuzufügen, welche die Daten z. B. auch in veränderter Form ausgeben.
Dies ist wie gesagt ein einfaches Beispiel, was aber die Funktionsweise von Observern gut veranschaulichen sollte. Es gibt unterschiedliche Implementierungen von Observen; so wird z. B. auch oft das Subjekt selbst als Parameter der notify-Methode übergeben, so dass ein Observer weiß, welches konkrete Subjekt ihn jetzt benachrichtigt hat (es kommt durchaus vor, dass ein Observer mehrere Subjekte beobachtet).

Auf eine Begebenheit soll hier am Ende noch eingegangen werden:
Was macht man eigentlich, wenn z. B. eine Klasse, die als Observer fungieren soll, schon in einer Vererbungslinie steht? Also was wäre, am obigen Beispiel erklärt, wenn ConcreteObserver schon von einer ganz anderen Klasse (z. B. einer GUI-Komponentenklasse) erben würde und man aber trotzdem auch von ObserverInterface erben muss? Dafür gibt es mehrere Lösungswege; ein sehr eleganter ist das bereits beschriebene Adapter Pattern. Das könnte dann z. B. so aussehen:



Wichtig sind hier eigentlich nur die beiden Klassen rechts (Window und MyWindow), das andere entspricht im Prinzip dem Code von vorhin.
Die Klasse "Window" soll hier aus einer GUI-Bibliothek entstammen und dient zur Darstellung von Fenstern. Will man etwas in ein solches Fenster zeichnen, dann muss man eine eigene Klasse erstellen, welche von "Window" erbt, und muss gewisse Methoden überschreiben, so dass man auch wirklich zeichnen kann (z. B. so etwas wie "paintEvent()", was es ja in einigen GUI-Bibliotheken gibt). Zu diesem Zweck gibt es hier die Klasse "MyWindow".
Genau hier liegt jetzt aber das Problem: Die Klasse "MyWindow" sollte hier ja eigentlich der Observer sein, welcher die vom Subject übermittelten Daten darstellen soll. D. h., MyWindow müsste sowohl von "ObserverInterface" als auch von "Window" erben.
Hier wurde aber stattdessen die Klasse ConcreteObserver beibehalten und die Klasse "MyWindow" adaptiert. Wird jetzt dieser Observer vom Subject benachrichtigt, so wird dieser Aufruf weiter an "MyWindow" delegiert, wo man dann z. B. zeichnen kann.




4.4 Das Strategy Pattern

Zu diesem Pattern sei folgendes (eher realitätsfernes, dafür aber verständliches) Beispiel gegeben:

Wir haben eine Klasse, welche einen großen Datenbestand enthält:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UserClass
{
private:
     int* data;
 
public:
   const int* getData() {  return data;   }
 
   void insertValueAt(int pos, int value)
  {
      if (pos < 10000 && pos >= 0)
          data[pos] = value;
   }
 
   UserClass() { data = new int[10000]; }
   ~UserClass() { delete[] data; }
};


Ja, diese Klasse ist nicht gerade sehr schön, aber darauf kommt es auch nicht an. Jedenfalls wäre es jetzt toll, wenn man diese große Menge an Daten auch sortieren könnte, so dass man beim Aufruf von getData() das sortierte Array zurückgeliefert bekommt. Wie wir ja alle wissen, gibt es verschiedene Sortieralgorithmen, z.B. QuickSort, ShellSort, SelectionSort, usw. Das heißt, wir könnten in unsere Klasse jetzt eine Methode sort() aufnehmen, welche unsere Daten dann mit einem dieser Algorithmen sortiert. Wir wollen uns jedoch nicht auf einen bestimmten Algorithmus festlegen, sondern wollen diesen vom Benutzer der Klasse vorgeben lassen, was dann z. B. so aussehen könnte:

C++:
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#define QUICKSORT         0
#define SHELLSORT         1
#define SELECTIONSORT     2
 
class MyClass
{
private:
     int* data;
 
public:
   const int* getData() {  return data;   }
 
   void insertValueAt(int pos, int value)
   {
      if (pos < 10000 && pos >= 0)
          data[pos] = value;
   }
   
 
   MyClass() { data = new int[10000]; }
   ~MyClass() { delete[] data; }
 
   void sort(int algorithmToUse)
   {
       switch (algorithmToUse)
       {
       case QUICKSORT:
           sortWithQuickSort();
           break;
       case SHELLSORT:
           sortWithShellSort();
           break;
       case SELECTIONSORT:
           sortWithSelectionSort();
           break;
       default:
           break;
       }
   }
 
 
 
   void sortWithQuickSort()
   {
       //Implementierung von QuickSort
   }
   
   void sortWithShellSort()
   {
       //Implementierung von ShellSort
   }
   
   void sortWithSelectionSort()
   {
       //Implementierung von SelectionSort
   }
 
       
};


Das ist natürlich eine äußerst unelegante Lösung. Jedes Mal, wenn ein neuer Algorithmus hinzukommt, müssen wir die Klasse bearbeiten und die switch-Struktur anpassen. Zudem ist hier die Laufzeitfehlerquote erhöht, da der Benutzer der Klasse ja auch falsche Werte übergeben kann.
Die Klasse selbst kann immer nur einen dieser Algorithmen zum Sortieren benutzen, d. h., sie braucht nicht all diese Algorithmen zu kennen. Wichtig ist nur, dass sie ihren Datenbestand sortieren kann. WIE (d. h. mit welchem Algorithmus) sie das tut, ist für die Klasse selbst eigentlich ziemlich egal.
Eine bessere Lösung ist es also, die jeweiligen Algorithmen in eigenen Klassen zu kapseln, was auch einem der Grundprinzipien von vielen Design Patterns entspricht: Beim Design einer Klasse schaut man, was sich immer mal wieder ändern kann (hier also z. B. die verschiedenen Sortieralgorithmen), und kapselt diese in neuen Klassen. Dadurch wird diese Klasse flexibler und man kann besser auf neue, sich ändernde Anforderungen reagieren.
Bei diesem Beispiel mit dem Strategy-Pattern sähe das dann so aus:



Natürlich ist es hier ein bisschen "Overkill", das Strategy Pattern anzuwenden, und es gäbe auch andere Möglichkeiten, dies elegant zu lösen, aber wie gesagt, daran sieht man gut, worauf es beim Strategy Pattern ankommt. Zur besseren Verständlichkeit hier noch der C++-Code:
C++:
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
38
39
class UserClass
{
private:
     int* data;
     SortStrategy* sorter;  // Die zu verwendende "Strategie"
 
public:
   const int* getData() {  return data;   }
 
   void insertValueAt(int pos, int value)
   {
      if (pos < 10000 && pos >= 0)
          data[pos] = value;
   }
 
   
   // Hier müssen wir jetzt der Klasse auch eine Sortierstrategie übergeben
   UserClass(SortStrategy* s) {
       data = new int[10000];
       sorter = s;
   }
 
   ~UserClass() {
       delete[] data;
   }
 
 
   void sort()
   {
           sorter->sort(data,10000);
   }
 
   // Hier kann man jetzt eine neue "Strategie" angeben, mit der sortiert werden soll
   void changeStrategy(SortStrategy* s)
   {
        sorter = s;        
   }
 
};


C++:
1
2
3
4
5
6
7
8
9
10
// Die abstrakte Basis-Klasse für alle Sortier-Implementierungen
class SortStrategy
{
 
public:
    virtual void sort(int* data, int len) = 0;
 
protected:
    SortStrategy() {}
};


C++:
1
2
3
4
5
6
7
8
9
10
11
#include "SortStrategy.h"
 
class QuickSort : public SortStrategy
{
public:
    QuickSort()  {}
 
    void sort(int* data, int len) {
        // Hier steht dann die Implementierung des Quicksort-Algorithmus
    }
};


C++:
1
2
3
4
5
6
7
8
9
10
11
#include "SortStrategy.h"
 
class ShellSort : public SortStrategy
{
public:
    ShellSort() {}
 
    void sort(int* data, int len) {
        // Hier steht dann die Implementierung des Shellsort-Algorithmus
    }
};



C++:
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
#include "UserClass.h"
#include "SortStrategy.h"
#include "QuickSort.h"
#include "ShellSort.h"
 
int main()
{
 
    SortStrategy* s = new ShellSort();
    UserClass* c = new UserClass(s);
    c->sort(); // mit Shellsort sortieren
 
    //Algorithmus wechseln
    c->changeStrategy(new QuickSort());
    c->sort(); // jetzt wird mit Quicksort sortiert
   
    // in C++ müssen wir selbst allozierte Speicherbereiche auch wieder freigeben:
        delete s;
        delete c;
   
    // ACHTUNG: Beim Aufruf von "c->changeStrategy(new QuickSort());" haben wir uns jedoch nicht die Speicheradresse des neuen QuickSort-Objekt gemerkt und
// können es somit auch nicht selbst wieder freigeben. D. h., hier würde ein Memory Leak entstehen, wenn das Programm noch länger laufen würde.
 
    return 0;
}



Ein häufig auftretender Fehler ist, dass in Fällen wie diesen Vererbung eingesetzt wird, um die verschiedenen Verhaltensweisen einer Klasse zu modellieren. Das ist jedoch falsch. Eine Vererbung ist immer eine Ist-ein-Beziehung, nicht mehr und nicht weniger. Durch das Kennen des Strategy-Patterns lassen sich solche Fehler eventuell vermeiden. Nehmen wir z. B. noch mal obiges Beispiel: Nehmen wir an, dass wir dieser Klasse noch eine Zeichenfunktion hinzufügen wollen, welche wahlweise die Häufigkeit des Auftretens der verschiedenen Zahlen im Datenbestand entweder als Balken- oder Kreisdiagramm zeichnet. Es gibt mitunter Leute, die hier auf die Idee kommen könnten, dies als Vererbung zu realisieren, was z. B. so aussehen könnte:

(Vererbung)


Viel besser wäre hier aber wieder die Anwendung des Strategy-Patterns. Auf welche Art und Weise (also wie) die Klasse so ein Diagramm zeichnet, ist egal, Hauptsache sie zeichnet es. So lassen sich auch bequem neue Zeichenimplementierungen hinzufügen bzw. bestehende verändern und das ohne unsere Ausgangsklasse zu modifizieren, was insbesondere in größeren Projekten wichtig ist. Ein weiterer Vorteil ist, dass die mithilfe des Strategy-Patterns gekapselten Algorithmen auch von anderen Klassen problemlos benutzt werden können.

(Strategy-Pattern)





5 Zusammenfassung

Gerade beim letzten Pattern, dem Strategy Pattern, kann man sehen, worauf es bei vielen Patterns ankommt. Man hat ein größeres Problem, das man mit einer oder mehreren Klassen zu lösen versucht. Dieses große Problem lässt sich in mehrere kleinere Teilprobleme unterteilen, von denen nicht alle anwendungsspezifisch, sondern allgemeiner sind, wie z.B . das Benachrichtigen von Objekten bei Zustandsänderungen (vgl. Observer Pattern). Diese kleineren Teilprobleme versucht man dann in eigene Klassen zu kapseln, was oft durch gemeinsame Schnittstellen und Polymorphie erreicht wird. Dadurch werden die Klassen, die man schreibt, um einiges flexibler und man wird besser auf sich ändernde Anforderungen reagieren können, was gerade heute ungemein wichtig ist.

Es ist allerdings nicht wichtig, jedes einzelne Design Pattern zu kennen. Viel wichtiger ist es, das prinzipielle Vorgehen bei Design Patterns zu verstehen und dieses auch im Alltag anwenden zu können. Man sollte also beim Entwickeln von Klassen ein großes Problem in kleinere "zerlegen" können, um dann zu schauen, ob es für so ein Teilproblem nicht vielleicht schon ein bewährtes Entwurfsmuster gibt.

Bei relationalen Datenbanken hat einer meiner Professoren einmal gesagt, dass die Antwort auf viele Probleme einfach im Erzeugen neuer Tabellen liegt. Genauso kann man beim objektorientierten Programmieren meiner Meinung nach behaupten, dass die Antwort auf viele Probleme im Erstellen neuer Klassen liegt, in welche man Teilprobleme auslagert. Klingt vielleicht ein bisschen dumm, aber da steckt schon ein bisschen Wahrheit drin.


6 Literatur

[1] Design Patterns - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
DAS Buch zu Design Patterns schlechthin. Jedes einzelne Design Pattern wird anhand von UML-Diagrammen, Code-Beispielen (C++; Smalltalk) und Problemstellungen durchgegangen. Für absolute Anfänger vielleicht eher weniger tauglich, ansonsten aber sehr gut. Gibts auch auf Deutsch.

[2] Design Patterns Explained - A New Perspective on Object Oriented Design - Allan Shalloway, James R. Trott
Meiner Meinung nach ein sehr schönes Buch, welches nicht nur einfach eine Auflistung aller Design Patterns von A-Z bringt, sondern vielmehr versucht, dem Leser anhand einiger ausgewählter Design Patterns einen guten OO-Stil beizubringen. Zudem ist das Buch sehr kurzweilig geschrieben. Alle Code-Beispiele gibts in Java und C++.

[3] Modern C++ Design: Generic Programming and Design Patterns applied - Andrei Alexandrescu
Dreht sich nicht ausschließlich um Design Patterns, sondern insbesondere auch um generische Programmierung mit Templates. Sollte hier aber dennoch nicht fehlen, da es bei den behandelten Design Patterns nicht nur einfach eine einfache Implementierung zeigt, sondern v. a. auch auf verschiedene Problemstellungen eingeht und dafür C++-bezogene Lösungswege zeigt. Ziemlich anspruchsvoll; ohne vorherige Erfahrung mit Templates und Design Patterns sehr schwer zu verstehen.

[4] Head first Design Patterns - Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra
Das Buch soll wohl sehr gut und vor allem auch angenehm zum Lesen sein (ich kenne es nicht).


Zuletzt bearbeitet von GPC am 10:48:25 21.11.2006, insgesamt 6-mal bearbeitet
KasF
Mitglied

Benutzerprofil
Anmeldungsdatum: 14.12.2004
Beiträge: 2492
Beitrag KasF Mitglied 14:19:39 09.08.2006   Titel:              Zitieren

Klasse Artikel :live: Hat mir sehr gut gefallen, habe schon immer auf sonen Artikel gewartet, obwohl nicht alles neu war :D

Werden auch weiter Teile hinzukommen oder wars das erstmal mit den Patterns ?

_________________
Um C++ zu beherrschen muss man schon for( ;; ) fragen ob er genug Zeit für einen hat ...
virtuell Realisticer
Mitglied

Benutzerprofil
Anmeldungsdatum: 07.05.2000
Beiträge: 3458
Beitrag virtuell Realisticer Mitglied 14:24:02 09.08.2006   Titel:              Zitieren

Absolut gelungener Artikel, sehr sehr gut :live:

gruss
v R

_________________
virtuell Realisticer, innen gut, aussen besser
THX 1138
Mitglied

Benutzerprofil
Anmeldungsdatum: 26.03.2006
Beiträge: 2327
Beitrag THX 1138 Mitglied 15:07:47 09.08.2006   Titel:              Zitieren

:live: Top, super Artikel
Sowas hatte ich auch noch vermisst.

Kann man sich auf die Fortsetzung freuen?
Wunschliste:
    Factory Method
    Abstract Factory
    Visitor

_________________
Tretet für die Gerechtigkeit ein, wenn ihr vor Gott Zeugnis ablegt, und sei es gegen euch selber oder euere Eltern und Verwandten. Handele es sich um arm oder reich, Gott steht euch näher als beide.
(Sure an-Nisa, 135)
hemem
Unregistrierter




Beitrag hemem Unregistrierter 18:34:56 09.08.2006   Titel:              Zitieren

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include "singleton.h" // Die Singleton-Klasse von oben
 
using namespace std;
 
int main()
{
    // Hier wird tatsächlich das Singleton-Objekt in der Instance-Methode instanziiert und zurückgegeben
    Singleton s1 = Singleton::Instance();      
 
    // Hier wird nun einfach das bereits weiter oben instanziierte Objekt zurückgegeben
    Singleton s2 = getSingletonInstance(); // !
   
    s1.doSomething();
 
    // Adressen des Objektes ausgeben: Diese sind immer gleich, d. h., es handelt sich immer um dasselbe Objekt
    cout << hex << &getSingletonInstance() << endl; // !
    cout << hex << &getSingletonInstance() << endl; // !
 
    return 0;
}

// ! fehlt da nicht ein Singleton::?
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 19:52:14 09.08.2006   Titel:              Zitieren

Das freut mich, dass der Artikel gut ankommt :)
Also prinzipiell spricht nichts gegen ein Fortsetzung, wobei ich demnächst aber einen anderen Artikel anfange. Aber danach, warum nicht :)

hemem schrieb:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include "singleton.h" // Die Singleton-Klasse von oben
 
using namespace std;
 
int main()
{
    // Hier wird tatsächlich das Singleton-Objekt in der Instance-Methode instanziiert und zurückgegeben
    Singleton s1 = Singleton::Instance();      
 
    // Hier wird nun einfach das bereits weiter oben instanziierte Objekt zurückgegeben
    Singleton s2 = getSingletonInstance(); // !
   
    s1.doSomething();
 
    // Adressen des Objektes ausgeben: Diese sind immer gleich, d. h., es handelt sich immer um dasselbe Objekt
    cout << hex << &getSingletonInstance() << endl; // !
    cout << hex << &getSingletonInstance() << endl; // !
 
    return 0;
}

// ! fehlt da nicht ein Singleton::?


Wenn du dir den Quellcode anschaust, dann siehst du, dass getSingletonInstance eine normale Funktion außerhalb der Singleton-Klasse ist (welche eben dazu dient, dass man nicht ständig über Singleton::Instance drauf zugreifen muss). D.h. da fehlt nichts :)
goofy
Unregistrierter




Beitrag goofy Unregistrierter 02:31:03 10.08.2006   Titel:              Zitieren

hi zu Observer Pattern hab ich gerade was gefunden:
http://www.ddj.com/dept/cpp/184403873
viel spaß;)
Maxi
Mitglied

Benutzerprofil
Anmeldungsdatum: 02.02.2003
Beiträge: 1883
Beitrag Maxi Mitglied 19:11:17 10.08.2006   Titel:              Zitieren

aber müsste ein singleton nicht auch den zuweisungsoperator protected oder private machen? Denn sonst kann man ja doch meherere Instnazen erstelln, wie man sieht:

Singleton s1 = getSIngletonInstance();
Singleton s2 = getSinbgleTonInstnace();

s1 und s2 sind verschiedene Insstanzen, weil die rüberkopiert worden sind. Da muss doch eigentlich ne Referenz hin, oder?
also Singleton& s1=getSingletonInsance();

oder?

_________________
Liebe Grüße :-)
http://www.myspace.com/diekotschafter
sdfsdf
Unregistrierter




Beitrag sdfsdf Unregistrierter 23:59:08 10.08.2006   Titel:              Zitieren

Maxi schrieb:
s1 und s2 sind verschiedene Insstanzen, weil die rüberkopiert worden sind. Da muss doch eigentlich ne Referenz hin, oder?
also Singleton& s1=getSingletonInsance();


Ganz genau, weil der Copy Constructor nicht beachtet wurde: http://msdn.microsoft.com/msdnmag/issues/03/02/CQA/
dia
Unregistrierter




Beitrag dia Unregistrierter 12:24:47 11.08.2006   Titel:              Zitieren

Mit welchem Programm hast du diese schönen Diagramme gemacht?
evilissimo
Chefkoch

Benutzerprofil
Anmeldungsdatum: 12.11.2003
Beiträge: 2281
Beitrag evilissimo Chefkoch 13:11:11 11.08.2006   Titel:              Zitieren

dia schrieb:
Mit welchem Programm hast du diese schönen Diagramme gemacht?


Die sehen aus als wären die mit dem Program gemacht worden das du als Nick trägst ;)

BR
Vinzenz

_________________
evilissimo - R.I.P. dmr
< Moderator im C++/CLI Forum und im C++ Forum >
About Singletons: "Anyway, if our experts can make this mistake, you have made it and you don't even know about it."
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 15:14:24 11.08.2006   Titel:              Zitieren

Oh ja das stimmt, den Copy-Konstruktor hab ich vergessen :rolleyes:
Den sollte man besser protected machen, hier in dem Beispiel spielts aber keine Rolle (zudem ist das C++-spezifisch, in andern Sprachen braucht man das nicht)

@dia
Die Diagramme hab ich mit ArgoUML erzeugt. Gibt aber schöneres ;)
evilissimo
Chefkoch

Benutzerprofil
Anmeldungsdatum: 12.11.2003
Beiträge: 2281
Beitrag evilissimo Chefkoch 15:25:45 11.08.2006   Titel:              Zitieren

nep schrieb:
Oh ja das stimmt, den Copy-Konstruktor hab ich vergessen :rolleyes:
Den sollte man besser protected machen, hier in dem Beispiel spielts aber keine Rolle (zudem ist das C++-spezifisch, in andern Sprachen braucht man das nicht)

@dia
Die Diagramme hab ich mit ArgoUML erzeugt. Gibt aber schöneres ;)


Ach echt? :eek: Die von DIA sehen auch so aus :p
BR
Vinzenz

_________________
evilissimo - R.I.P. dmr
< Moderator im C++/CLI Forum und im C++ Forum >
About Singletons: "Anyway, if our experts can make this mistake, you have made it and you don't even know about it."
Tc++H
Mitglied

Benutzerprofil
Anmeldungsdatum: 23.09.2005
Beiträge: 581
Beitrag Tc++H Mitglied 21:59:54 11.08.2006   Titel:              Zitieren

Sehr schöner Artikel, könntest du eventuell auch noch ein paar exoten erläutern?
Hmmm...
Unregistrierter




Beitrag Hmmm... Unregistrierter 09:36:44 12.08.2006   Titel:              Zitieren

Warum machst du die doSomeThing Funktion static? Wenn das Singleton static ist, dann muss doch die Funktion nicht mehr static sein.
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 14:46:34 12.08.2006   Titel:              Zitieren

Gute Frage :rolleyes:
Ist natürlich unnötig, auch wenn sich nichts dran ändert
Observer-Newbie
Unregistrierter




Beitrag Observer-Newbie Unregistrierter 13:39:05 21.08.2006   Titel:              Zitieren

Ich hab paar Fragen zu dem Observer-Pattern.

Es ist ja für mich eigentlich schon verwirrend genug, dass nicht das Subject der eigentliche Observer ist, weil er reagiert ja eigentlich auf die Zustandsänderung, indem er die Observer benachrichtigt. Die Observer beobachten für mich also eigentlich gar nichts, sondern warten eher, bis sie mal von der Seite angestubst werden.

Aber ok, was ich eigentlich wissen wollte, warum bekommen die Observer einen Zeiger des Subjects, das sie benachrichtigt? Warum müssen die den haben?
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 12:09:33 22.08.2006   Titel:              Zitieren

Naja da hast du programmiertechnisch gesehen auch nicht unrecht. Das Subjekt muss die Observer natürlich benachrichtigen (also wie du es nennst "von der Seite anstubsen"). Aber genau das ist es ja was die Observer "beobachten", sie sind abhängig vom Subject und beobachten Zustandsänderungen. Wenn sich aber etwas am Zustand geändert hat, dann muss das Subjekt dieses den Observern auch irgendwie mitteilen; die können das ja nicht riechen ;)

Und genau deswegen bekommen auch die Observer einen Zeiger des Subjects. Sobald sie benachrichtigt wurden, wissen sie ja, dass sich etwas am Zustand des Subjektes geändert hat. Und nun will ein Observer normalerweise natürlich auch wissen was sich am Zustand geändert hat (also z.B. neue Daten usw...), und diese Änderungen muss man beim Subject abfragen, und das geht eben über diesen Zeiger
Erhard Henkes
Mitglied

Benutzerprofil
Anmeldungsdatum: 25.04.2000
Beiträge: 12169
Beitrag Erhard Henkes Mitglied 00:00:41 04.09.2006   Titel:              Zitieren

Zu Observer:
http://www.henkessoft.de/ ....... rittene.htm#3.1._Observer

_________________
OS-Development-, C++, Win32-API-, MFC-, Chemie-, Robotik- und Flugsimulator-Tutorials
http://www.henkessoft.de/index.htm
Smiling
Unregistrierter




Beitrag Smiling Unregistrierter 21:39:37 04.03.2007   Titel:              Zitieren

Gibt es das Programm (bei den Strategy Mustern) auch komplett (also lauffähig) zum ausprobieren irgendwo, ohne das ich noch die Sortieralgorythmen einfügen muss und so, weil ich damit irgendwie probleme habe...
Fedaykin
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.01.2007
Beiträge: 882
Beitrag Fedaykin Mitglied 11:55:04 06.03.2007   Titel:              Zitieren

Hmm der artikel ist sehr gut, vor allem das strategie pattern ist interessant sowie das mit den observern und eigentlich das mit den adaptern auch. Obwohl man dort hätte auch selber drauf kommen können. Wenn ich einen vorschlag machen dürfte was ggf noch mit rein könnte, wären Factory classes. Ich setze die selber ab und an mal ein und die sind sehr hilfreich.
Artchi
Autor

Benutzerprofil
Anmeldungsdatum: 16.03.2002
Beiträge: 8659
Beitrag Artchi Autor 12:27:49 06.03.2007   Titel:              Zitieren

Fedaykin schrieb:
Obwohl man dort hätte auch selber drauf kommen können.

Jeder der schon länger programmiert und noch nie etwas über Pattern gelesen/gehört hat, hat schon mal unwissend diese u.ä. Patterns genutzt. Das liegt in der Natur der Sache... sprich in der Natur der Objektorientierung. Patterns sind einfach nur noch mal eine schriftliches festhalten.
Sovok
Mitglied

Benutzerprofil
Anmeldungsdatum: 12.08.2002
Beiträge: 2013
Beitrag Sovok Mitglied 10:53:11 20.08.2007   Titel:              Zitieren

super artikel :live:
d_496
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.09.2007
Beiträge: 2
Beitrag d_496 Mitglied 08:41:55 12.09.2007   Titel:   Frage zu Observer Pattern mit GUIs.            Zitieren

Hallo,

dem kann ich mich nur anschließen. Super Artikel, vor allem mit den
minimalen C++ Gerüsten, die man direkt verwenden kann.
Ich habe eine Frage zum Observer-Pattern, und zwar der Abbildung
unten zum Thema GUIs, wo auch das Adapter-Pattern mit einfließt.
Muß da nicht die Aggregation zwischen ConcreteObserver und MyWindow
anders herum sein, da ja mein ConcreteObserver ein Handle auf MyWindow
braucht, um dessen Schnittstelle zu adaptieren, aber umgekehrt
MyWindow kein Handle auf den ConcreteObserver haben muß? Der
ConcreteObserver wäre hier also die Gesamtheit und MyWindow ein Teil
dieser Gesamtheit. Die Raute müßte also bei ConcreteObserver sein?
Ich bin nämlich gerade dabei, genau dieses Muster anzuwenden und es
würde mir sehr helfen, da Klarheit zu kriegen.

Weiter so und beste Grüße,

Peter.
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 09:39:43 13.09.2007   Titel:              Zitieren

Danke erst mal.

@d_496:
Ja, du hast mit allem vollkommen recht, ConcreteObserver adaptiert die Schnittstelle von MyWindow. Da hab ich die Aggregation im UML-Diagram falsch rumgesetzt. Wie du schon sagtest, müsste die Raute genau anders rum sein. Werd ich wohl heute abend noch korrigieren.

@Smiling:
Ich hab glaube ich irgendwo noch das Projekt rumfahren, aber glaub auch ohne konkrete Sortieralgorithmen. Wo sind denn deine Probleme?
Mr. N
Mitglied

Benutzerprofil
Anmeldungsdatum: 28.12.2001
Beiträge: 4328
Beitrag Mr. N Mitglied 13:10:34 16.09.2007   Titel:              Zitieren

Wieso ist eigentlich alles auf dem Heap? Und dann auch noch ohne Smart-Pointer?

Das ist doch ein sicheres Rezept für unnötige Fehler!
Don06
Mitglied

Benutzerprofil
Anmeldungsdatum: 26.09.2006
Beiträge: 715
Beitrag Don06 Mitglied 20:15:56 18.09.2007   Titel:              Zitieren

Vielleicht hat er früher mal Java/C# oder ähnliches programmiert. Sowas sieht man oft, das solche Leute dann mit Value-Semantik und RAII Probleme haben.

Gruß
Don06
nep
Autor

Benutzerprofil
Anmeldungsdatum: 20.12.2002
Beiträge: 913
Beitrag nep Autor 17:06:11 19.09.2007   Titel:              Zitieren

Aha...

Nun ja, warum soll ich da Smart Pointer reinbringen? Das verwirrt viele Leute eher, da eher wenige Leute mit SmartPointer arbeiten, und ich mich auch nicht zu sehr auf C++ fokussieren wollte.

Und wenn man mit Polymorphie arbeitet, dann muss das Zeugs nun mal dynamisch allokiert werden
Mr. N
Mitglied

Benutzerprofil
Anmeldungsdatum: 28.12.2001
Beiträge: 4328
Beitrag Mr. N Mitglied 00:38:24 23.09.2007   Titel:              Zitieren

nep schrieb:
Aha...

Nun ja, warum soll ich da Smart Pointer reinbringen? Das verwirrt viele Leute eher, da eher wenige Leute mit SmartPointer arbeiten, und ich mich auch nicht zu sehr auf C++ fokussieren wollte.

Und wenn man mit Polymorphie arbeitet, dann muss das Zeugs nun mal dynamisch allokiert werden


Smart-Pointer wären IMHO besser, ja. Dieses schreckliche übermäßige Verwenden von rohen Pointern, obwohls in C++ nicht nötig ist, sorgt in meiner Erfahrung für enorm viele Fehler.

Zum Thema "nicht auf C++ fokussieren"... wenn du C++ nicht nutzen willst, nimm ne andere Sprache, aber bitte kein verkrüppeltes C++.
Sovok
Mitglied

Benutzerprofil
Anmeldungsdatum: 12.08.2002
Beiträge: 2013
Beitrag Sovok Mitglied 10:22:16 23.09.2007   Titel:              Zitieren

ich bin auch gegen smart pointer aus ähnlichem grund wie nep
einfach weil die meißten c++ mit rohen pointern gelernt haben und
die beispiele mit rohen pointern verständlich und durchsichtig sind


dass smart pointer in realer umgebung sinnvoller sind ist richtig aber
ein ganz anderes thema


Zuletzt bearbeitet von Sovok am 10:22:28 23.09.2007, insgesamt 1-mal bearbeitet
RAII ist der einzige Weg
Unregistrierter




Beitrag RAII ist der einzige Weg Unregistrierter 19:08:03 30.07.2008   Titel:              Zitieren

Sovok schrieb:
ich bin auch gegen smart pointer aus ähnlichem grund wie nep
einfach weil die meißten c++ mit rohen pointern gelernt haben und
die beispiele mit rohen pointern verständlich und durchsichtig sind


dass smart pointer in realer umgebung sinnvoller sind ist richtig aber
ein ganz anderes thema

Tut mir leid aber das ist großer Mist.
Zum einen würde es sicher ausreichen kurz zu erläutern, was ein smart pointer ist.
Beu Tutorials ist es wichtig immer den richtigen und sichersten weg zu wählen, damit andere davon lernen können. Warum also nicht smart pointer benutzen. An der Verständnis kann es sicherlich nicht liegen, denn:
Foo *bar = new Foo();
bar->blub()
oder
auto_ptr<Foo>bar(new Foo());
bar->blub();
ist alles andere als ein riesiger schwer zu verstehende Unterschied.
Das einzige was nötig wäre, ist dem Leser kurz zu vermitteln was so ein Zeiger Container macht. Also bei zerstörung des containers automatisch den Inhalt freigeben und somit Speicherlecks zu verhindern und ansonsten die Nutzung des Objektes wie immer von statten geht.
Das ist ebenso verständlich wie mit rohen pointern plus zusätzlicher Sicherheit. Solche Grundlegenden Dinge sollten auch in so einem Tut genutzt und wenn nötig kurz erklärt werden.

Wenn etwas beibringen, dann auch richtig.

Schöne Grüße
bEKAR
Mitglied

Benutzerprofil
Anmeldungsdatum: 29.11.2009
Beiträge: 6
Beitrag bEKAR Mitglied 21:44:16 29.11.2009   Titel:   kleiner Rechtschreibfehler            Zitieren

also erstmal Danke für diesen Artikel! Habe dadurch endlich nach 2 Tagen eine funktionierende Observer Subject Struktur hinbekommen. Es gibt noch einen kleinen Fehler

class ConcrecteSubject : public Subject

da hast du ein c zu viel. Ansonst Danke für diesen Artikel ich teste jetzt mal das Strategie Pattern!

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // ConcreteSubject.h //
#include <string>
#include "Subject.h"
 
using namespace std;
 
class ConcrecteSubject : public Subject
{
 
private:
    string data;
 
public:
    void setData(string _data) { data = _data; }
    string getData() { return data; }
    ConcreteSubject() : Subject() {}
};



Gruß bEKAR
phcn.fraggle
Mitglied

Benutzerprofil
Anmeldungsdatum: 28.08.2008
Beiträge: 166
Beitrag phcn.fraggle Mitglied 20:14:26 21.04.2010   Titel:              Zitieren

Super Artikel, genau danach habe ich gesucht und auf anhieb gefunden ;) :live:

_________________
Mit freundlichem Gruß,fraggle[at]phcn[dot]de ( Jabber )
Freeunix.net - We host your source!
[PHCN]Jabber Service
tomislaw
Unregistrierter




Beitrag tomislaw Unregistrierter 11:34:59 09.09.2010   Titel:              Zitieren

Super Artikel, danke auch von meiner Seite! :live:
fr33g
Mitglied

Benutzerprofil
Anmeldungsdatum: 07.01.2010
Beiträge: 809
Beitrag fr33g Mitglied 12:15:38 10.09.2010   Titel:              Zitieren

Sehr guter Artikel, dickes Dankeschön auch von mir :live:

Lg freeG
c++.de :: Die Artikel ::  Einführung in Design Patterns   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 und www.c-plusplus.net 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.