Willkommen zum fünften Teil der „Design Patterns in der Praxis“ Serie. Der Heutige Eintrag befasst sich mit dem „Observer Pattern“ (bzw. „Beobachter“). Mit dem Observer Pattern können wir an bestimmten Stellen unserer Entwicklung einen Callback definieren.

Es ist leider etwas schwierig ein gutes Beispiel für dieses Pattern zu finden, weshalb ich diesen Eintrag schon einige Wochen vor mir her schiebe…

Ein gutes Beispiel konnte ich dann aber letztendlich (auf php::bar) doch finden: Die sogenannte „Auto“-Klasse.
Wir stellen uns vor wir haben ein Auto mit einer variable „tank“ – Das Auto kann nur fahren, wenn im Tank noch mehr als 0 liter Benzin ist.
Wird ein Wert von unter 5 litern erreicht, möchten wir benachrichtigt werden (z.B. in Form der „Reserve“-Anzeige).

Unsere Klasse sieht also etwa so aus:

// Unser "Auto" verfügt aktuell über einen Tank und die "fahren" Methode.
class Auto
{
    public $tank = 55;

    /**
     * Der "fahren" Methode geben wir als Parameter die Strecke mit.
     * 
     * @param  integer  $strecke
     */
    public function fahren($strecke)
    {
        // Um das Beispiel einfach zu halten ist eine "Strecken-Einheit" = 1 liter.
        $this->tank -= $strecke;

        if ($this->tank <= 0)
        {
            $this->tank = 0;
            echo 'Der Wagen ist liegengeblieben!';
        }
        else
        {
            echo 'Im Tank befinden sich noch ' . $this->tank . ' liter.';
        }
    }
}

Wir können also mit unserem vollgetankten Auto eine Strecke von 55 zurücklegen…

// Ein einfacher kleiner Test...
$auto = new Auto;
$auto->fahren(10);
$auto->fahren(20);
$auto->fahren(10);
$auto->fahren(20);

Im Browser würde uns nun etwa sowas hier ausgegeben werden:

Im Tank befinden sich noch 45 liter.
Im Tank befinden sich noch 25 liter.
Im Tank befinden sich noch 15 liter.
Der Wagen ist liegengeblieben!

Wir sind nun also mitten auf der Straße liegengeblieben – Schöner Mist! Damit sowas nicht passiert, bauen wir einen Observer ein, der unseren Tank überwacht und uns vorwarnt! Dieser kann etwa so aussehen…

// Unsere Observer-Klasse.
class reserve_anzeige
{
    public function beobachten($auto)
    {
        if ($auto->tank < 5)
        {
            echo "Achtung! Tank ist leer - Zur nächsten Tankstelle und tanken...";
            $auto->tank = 55;
        }
    } 
}

Nun müssen wir unsere „reserve_anzeige“ noch in unser Auto einbauen 😉 Dazu müssen wir unser Auto etwas erweitern… Wir müssen eine Methode erstellen um neue Teile einzubauen („observer_einbauen“) und diese auch zu benutzen („observer_rufen“). Das sieht dann etwa so aus:

// Unser "Auto" verfügt nun über eine Reserve-Anzeige!
class Auto
{
    public $tank = 55;

    protected $observer = array();

    /**
     * Methode zum hinzufügen neuer Observer
     * 
     * @param  object  $observer
     */
    public function observer_einbauen($observer)
    {
        $this->observer[] = $observer;
    }
	
    /**
     * Methode zum abrufen unserer hinzugefügen Observer.
     */
    private function observer_rufen()
    {
        foreach ($this->observer as $observer)
        {
            $observer->beobachten($this);
        }
    }

    /**
     * Der "fahren" Methode geben wir als Parameter die Strecke mit.
     * 
     * @param  integer  $strecke
     */
    public function fahren($strecke)
    {
        // Um das Beispiel einfach zu halten ist eine "Strecken-Einheit" = 1 liter.
        $this->tank -= $strecke;
		
        $this->observer_rufen();

        if ($this->tank <= 0)
        {
            $this->tank = 0;
            echo 'Der Wagen ist liegengeblieben!';
        }
        else
        {
            echo 'Im Tank befinden sich noch ' . $this->tank . ' liter.';
        }
    }
}

Nachdem wir unseren neuen Observer eingebaut haben können wir das Auto erneut auf die Reise schicken:

// Ein einfacher kleiner Test...
$auto = new Auto;
$auto->observer_einbauen(new reserve_anzeige);
$auto->fahren(10);
$auto->fahren(20);
$auto->fahren(10);
$auto->fahren(20);

Ergibt nun folgende Ausgabe:

Im Tank befinden sich noch 45 liter.
Im Tank befinden sich noch 25 liter.
Im Tank befinden sich noch 15 liter.
Achtung! Tank ist leer - Zur nächsten Tankstelle und tanken...
Im Tank befinden sich noch 55 liter.

Wir können nun beliebig viele Observer an unser Auto hängen! Allerdings gibt es noch ein Problem… Wir können verschiedene Observer-Klassen anhängen – Da ist nicht immer sichergestellt das unsere Methode zum beobachten auch tatsächlich „beobachten“ heißt! Also bauen wir ein Interface ein, welches unseren Klassen eine bestimmte Struktur vorgibt (Alle unsere Observer müssen eine „beobachten“ Methode haben).

// Unsere verbesserte Observer-Klasse mit Interface.
interface observer
{
    public function beobachten($auto);
}

class reserve_anzeige implements observer
{
    /**
     * In dieser Methode passiert die eigentliche Logik.
     *
     * @param  object  $auto
     */
    public function beobachten($auto)
    {
        if ($auto->tank < 5)
        {
            echo "Achtung! Tank ist leer - Zur nächsten Tankstelle und tanken...";
            $auto->tank = 55;
        }
    } 
}

So… Nun haben wir aber das Problem das unsere zu beobachtenden Objekte nicht zwingend eine „observer_einbauen“ oder „observer_rufen“ Methode haben müssen… Also was tun wir? Wir implementieren für unser Auto ebenfalls ein Interface, welches vorgibt wie unsere Klasse aussehen muss:

// Ein weiteres Interface für unser Auto!
interface observeable
{
    public function observer_einbauen(observer $observer);
    public function observer_rufen();
}

class Auto implements observeable
{
    // ...

Dadurch das unsere Klassen nun beide jeweils ein Interface benutzen, können wir mit Typecasting arbeiten und noch mehr Sicherheit einbringen! Den fertigen und kompletten Quellcode könnt ihr euch bei den Snippets ansehen!

Ich möchte mich an dieser Stelle bei php::bar bedanken, bei denen ich das tolle Beispiel gefunden habe! Auf der Seite findet ihr übrigens noch einige andere Design Patterns! Der Heutige Eintrag ist übrigens etwas „zusammengeschustert“ und „in letzter Minute“ entstanden weil ich die letzten Tage relativ wenig Zeit zur Vorbereitung gefunden habe – Entschuldigt!

Nächste Woche möchte ich dann wieder einen kleinen Abstecher in ein anderes Themengebiet machen! Thema wird „Saubere Entwicklung“ sein… Lasst euch überraschen 🙂

Ich wünsche noch eine angenehme Woche, ich werde mir gleich noch einen Tee aufsetzen – Bei den Minus Graden draußen scheine ich mich erkältet zu haben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.