Facebook
Twitter
Google+
Kommentare
5

Template Method (Schablonenmethode)

Einmal Kaffee bitte,

dazu noch eine Tasse Tee und eine heiße Schokolade! Wir los zu unserem Kaffeevollautomaten um die verschiedenen Getränke zuzubereiten. Oh, leider kann die Maschine die heiße Schokolade nicht, oder besser gesagt noch nicht – denn wir haben ja keine 08/15 Ausgabe eines Kaffeevollautomaten in der Küche stehen, sondern einen modernen den man selbst programmieren kann.

Da ich voller Vorfreude, als der Automat geliefert wurde, etwas bei der Implementierung der Getränke geschlampt hatte, wird es jetzt einiges schwerer das fehlende Getränk schnell noch mit einzubauen. Also kopiere ich die bestehende Klasse des Kaffees und passen die einzelnen Methoden an. Jetzt noch schnell eine Taste des Kaffeevollautomaten mit der neuen Klasse belegt und testen. Doch leider ist irgendwas schief gelaufen. Die heiße Schokolade wird mit Bohnen statt mit Kakao zubereitet. Da ist wohl der typische Copy-and-Paste Fehler aufgetreten. Was hätte man da besser machen können?

Zuerst untersuchen wir einmal die bestehenden Klassen:

class Kaffee {
    public function kochen() {
        $this->genugKaffeeVorhanden();
    }

    private function genugKaffeVorhanden() {
        if (BohnenTank::istLeer()) {
            $this->mahleKaffee();
        } else {
	    die("Bohnetank leer!");
        }
    }

    private function mahleKaffee() {
        Mahlwerk::mahle(5);
        $this->brueheWasser();
    }

    private function brueheWasser() {
        Heizspirale::an();
        sleep(10000);
        Heizspirale::aus();
        $this->filtereKaffee()
    }

    private function filtereKaffee() {
        Mahlwerk::fuelleKaffeepulver();
        Wassertank::oeffneHeissWasser();
	sleep(5000);
        Wassertank::schliesseHeissWasser();
	$this->fuelleTasse();
    }

    private function fuelleTasse() {
        Tassenhalter::stelleTasse();
        Tassenhalter::oeffneFilterVentil();
    }
}
class Tee {
    public function bruehen() {
          $this->genugTeeVorhanden();
    }

    private function genugTeeVorhanden() {
        if (Teebehaelter::istLeer()) {
            $this->wiegeTeeAb();
        } else {
	    die("Teebehaelter leer!");
        }
    }

    private function wiegeTeeAb() {
        Teewaage::wiegen(50);
        $this->brueheWasser();
    }

    private function brueheWasser() {
        Heizspirale::an();
        sleep(10000);
        Heizspirale::aus();
        $this->lasseZiehen();
    }

    private function lasseZiehen() {
        sleep(12000);
        $this->fuelleTasse();
    }

    private function fuelleTasse() {
        Tassenhalter::stelleTasse();
        Tassenhalter::oeffneFilterVentil();
    }
}
Kaffee.php Tee.php
kochen() bruehen()
genugKaffeVorhanden() genugTeeVorhanden()
mahleKaffee() wiegeTeeAb()
brueheWasser() brueheWasser()
filtereKaffee()
fuelleTasse() fuelleTasse()

Die Funktionalität und der Ablauf der beiden Klassen decken sich fast ab. Findet man nun einen Nenner, der die beiden Vorgänge beschreibt, könnte man diesen auch für die heiße Schokolade verwenden.

Kaffee.php Tee.php HeissGetraenk.php
kochen() bruehen() zubereiten()
genugKaffeVorhanden() genugTeeVorhanden() pruefeZutat()
mahleKaffee() wiegeTeeAb() bestimmeMenge()
brueheWasser() brueheWasser() brueheWasser()
filtereKaffee()
fuelleTasse() fuelleTasse() fuelleTasse()

Die beiden Funktionen brueheWasser und fuelleTasse sind bei den beiden Vorgängen identisch. Diese beiden Funktionen könne in eine HeissGetraenk.php Klasse aufgenommen werden. Die anderen Funktionen werden als abstrakt deklariert, die die beiden Klassen Tee und Kaffee konkret implementieren müssen. Die Funktion zubereiten ist die Zentrale Funktion, die die einzelnen Schritte der Zubereitung in der richtigen Reihenfolge nacheinander aufruft.

Hier die abstrakte HeissGetraenk.php Klasse:

abstract class HeissGetraenk {

    public function zubereiten() {
        $this->pruefeZutat();
        $this->bestimmesMenge();
        $this->brueheWasser();
        $this->fuelleTasse();
    }

    protected abstract function pruefeZutat();

    protected abstract function bestimmeMenge();

    protected function brueheWasser() {
        Heizspirale::an();
        sleep(10000);
        Heizspirale::aus();
    }

    protected function fuelleTasse() {
        Tassenhalter::stelleTasse();
        Tassenhalter::oeffneFilterVentil();
    }

}

Erstellt man nun eine neue Kaffeeklasse, die von der HeissGetraenk.php Klasse erbt, muss diese die beiden Funktionen pruefeZutat und bestimmeMenge implementieren. Übernimmt man die Funktionalität der bestehenden Klasse kann schnell und einfach die neue Klasse erstellt werden. Möchte man nun testen wie der Kaffee schmeckt, wird man schnell feststellen, dass die Tasse nur mit heißem Wasser gefüllt wird.

include "HeissGetraenk.php";

class KaffeeGetraenk extends HeissGetraenk {

    protected function pruefeZutat() {
        if (BohnenTank::istLeer()) {
	    die("Bohnetank leer!");
        }
    }

    protected function bestimmeMenge() {
	Mahlwerk::mahle(5);
    }
}

Was haben wir bloß falsch gemacht? Die Basisklasse HeissGetraenk berücksichtigt nicht das Filtern des Kaffees. Sie ruft nach brueheWasser die Funktion fuelleTasse auf, dadurch wird die filtereKaffee Funktion übersprungen. Wir müssen den Aufruf der Funktion filtereKaffee noch in den Ablauf der Zubereitung an der richtigen Stelle einbauen. In der alten Kaffee.php Klasse wird das Filtern nach dem Aufbrühen des Wassers aufgerufen. Das Brühen übernimmt jedoch unsere Basisklasse, dies stellt jedoch kein Problem dar, da wir einfach diese Funktion überschreiben können. Um nicht die Funktionalität des Brühens zu verlieren, muss in der neue Funktion die Basisfunktion mit parent::brueheWasser() aufgerufen werden. Darüber kann einfach die filtereKaffee Funktion aufgerufen werden.

include "HeissGetraenk.php";

class KaffeeGetraenk extends HeissGetraenk {

    protected function pruefeZutat() {
        if (BohnenTank::istLeer()) {
	    die("Bohnetank leer!");
        }
    }

    protected function bestimmeMenge() {
	Mahlwerk::mahle(5);
    }

    protected function brueheWasser() {
        parent::brueheWasser();
        $this->filtereKaffee();
    }

    private function filtereKaffee() {
        Mahlwerk::fuelleKaffeepulver();
        Wassertank::oeffneHeissWasser();
	sleep(5000);
        Wassertank::schliesseHeissWasser();
    }

}

Nach diesem Überschreiben der Funktion brueheWasser, schmeckt der Kaffee nun auch. Jetzt stellt das Programmieren einer HeissenSchokoladen Klasse auch kein Problem mehr dar.

Hier nochmal der Ablauf wenn die Kaffeetaste gedrückt wird:

example

Über den Autor

Daniel Jahnke

Kommentare

5 Comments

  1. Im ersten Beispiel der Klasse „Kaffe“, in der Methode „private function genugKaffeVorhanden()“ wird „mahleKaffe()“ immer nur dann aufgerufen, wenn „istLeer()“ boolean true liefert – demnach kann sie keinen Kaffe kochen sondern nur heißes Wasser machen 😉

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen