Anzeige
Tutorialbeschreibung

Entwurfsmuster: Strategie (Design Pattern: Strategy)

Entwurfsmuster: Strategie (Design Pattern: Strategy)

Inhalt dieses Tutorials: nach einer Kurzeinführung in das Thema "Entwurfsmuster" wird das Strategie- / Strategy-Muster erst abstrakt, dann am Beispiel und mit Implementierung erläutert. Vorkenntnisse in der Objektorientierung (in PHP5+) sind nötig.


Vorbemerkung:
Erstens kann das folgende Thema problemlos auf viele andere Hochsprachen angewandt werden. Zweitens sind die meisten Aussagen an das Buch "Entwurfsmuster" von Gamma, Helm, Johnson und Vlissides angelehnt. Drittens sollte der Leser Vorkenntnisse mit der Objektorientierung in PHP5+ und den grundlegenden Prinzipien der OOP haben. Das große Vorschaubild ist von Flickr-Benutzer "hellolapomme" (CC-BY).

Einleitung:
Dieses Tutorial behandelt ein fortgeschrittenes, essentielles Prinzip der Softwaretechnologie: Entwurfsmuster (Design Patterns). Genauer, in diesem Fall möchte ich das Muster "Strategie" (Strategy) als einen Vertreter beleuchten. Dennoch gibt es eine minimale Erklärung zu Entwurfsmustern vorneweg, direkt von Wikipedia zitiert:

Entwurfsmuster (engl. design pattern) sind bewährte Lösungs-Schablonen für wiederkehrende Entwurfsprobleme der Softwarearchitektur und Softwareentwicklung. Sie stellen damit eine wiederverwendbare Vorlage zur Problemlösung dar, die in einem spezifischen Kontext einsetzbar ist.
Im Klartext: eine Gruppe von schlauen Menschen hat sich zusammengefunden und Standardlösungen zu ständig auftretenden Problemen in der Softwareentwicklung aufgeschrieben (Stichwort "Gang of Four"). Das Rad soll also nicht immer neu erfunden werden.

 

Was nützen dem geneigten PHP-Scripter / Hochsprachenprogrammierer nun Entwurfsmuster? Die kurze, langweilige Erklärung: sie funktionieren. Für die lange Erklärung sei dem Leser das berühmte Buch der Gang of Four (s.o.) an das Herz gelegt, aber auch eine kurze Internetsuche gibt einen Einblick in deren Vorzüge. Genug der Vorrede, auf zum eigentlichen Thema:

Motivation:
Nehmen wir an, wir wollen ein sehr einfaches Browser-Spiel entwickeln1, zum Beispiel Schach gegen einen computergesteuerten Gegner. Ein wichtiger Aspekt davon ist sicherlich, wie der Gegner agiert. Nehmen wir weiterhin an, wir wüssten auf Anhieb mindestens 3 verschiedene, grundlegende Vorgehensweisen (Strategien!) dieses Schachgegners und könnten sie algorithmisch formulieren, z.B. "offensiv", "defensiv", "berserk". Wie verbinden wir diese Algorithmen mit dem Gegner?

Der erste Ansatz: Wir packen alle 3 Algorithmen in die Klasse.
<?php
class ComputerGegner {
private 
$figuren = array(=> "Bauer", etc.); 
public function 
__construct() {
    
//Konstruktor
    
}
    private function 
offensiv() {
        
// Algorithmus für offensive Strategie
    
}
    private function 
defensiv() {
        
// Algorithmus für defensive Strategie
    
}
    private function 
berserk() {
        
// Algorithmus für berserk Strategie
    
}
}
?>

Problem: was, wenn wir diese Algorithmen auch für andere Dinge einsetzen wollen? Den Code zu kopieren (buuuh!) oder eine Oberklasse zu bilden (besser, aber kann schnell zum Overkill mutieren, außerdem bildet man damit eine engere Beziehung zwischen zwei Klassen ab als vielleicht gewünscht) sind nicht ideal. Problem 2: die Klasse wird bei vielen (und aufwändigen) Algorithmen schnell unübersichtlich und damit wartungsunfreundlich. Was also tun?

Das Strategy-Muster:
Das Strategy-Muster kapselt verwandte Algorithmen einzeln ab und bietet eine gemeinsame Schnittstelle an. Schauen wir es uns erstmal im abstrahierten Klassendiagramm an:

Strategy-PatternBilder

Die Bestandteile sind im Einzelnen:

  • Kontext: "besitzt" / referenziert ein Strategie-Objekt, auf den Kontext wird (i.d.R.) die Strategie angewandt
  • Strategie: deklariert eine Schnittstelle, die für alle Kind-Strategien gültig ist und die letztendlich die angebotene Funktionalität ausführt
  • KonkreteStrategieA/B: echte Implementationen der gewünschten Funktionalität

Angewandt auf unser erstes Beispiel sähe das ungefähr so aus:
Strategy-Pattern am BeispielBilder

Jeder ComputerGegner hat also eine Vorgehensweise / Strategie, die die unterschiedlichen Züge berechnen kann, je nach Verhalten. Die o.g. Probleme sind damit passé: jede weitere Klasse kann sich ein Strategie-Objekt schnappen und seine Algorithmen nutzen ohne je von "ComputerGegner" gehört zu haben. Es werden keine künstlichen Oberklassen gebraucht, um die Strategien mehrfach zu nutzen. Außerdem bleiben die einzelnen Klassen einigermaßen sauber und werden nicht aufgebläht.

 

Implementation:
Das Schach-Beispiel ist etwas zu groß für ein griffiges Beispiel, deswegen ein sehr viel simpleres Problem: es gibt einen Rechner mit einem Array von Zahlen. Jetzt möchten wir gerne die Summe und das Produkt dieser Zahlen berechnen2 und dabei die einzelnen Algorithmen sauber vom Kontext trennen, natürlich mit dem Strategy-Muster.

Erst schreiben wir wie gewohnt die Rechner-Klasse als Kontext:
<?php
class Rechner {
  public 
$zahlen;
  public function 
__construct($array) {
    
$this->zahlen $array;
  }
}
?>

Jetzt bauen wir uns die einzelnen Strategien und definieren für das Beispiel, dass die Funktion berechneZahlen($array) als Schnittstelle dient und später die nötigen Berechnungen durchführt. Die Oberklasse "Algorithmus" und seine im Beispiel einzige Funktion sind dabei abstrakt, das kann aber natürlich von Fall zu Fall entschieden werden.
<?php
abstract class Algorithmus {
  abstract public function 
berechneZahlen($array);
}
?>

<?php
class Addierer extends Algorithmus {
    public function 
berechneZahlen($array) {
        
$erg 0;
        foreach(
$array as $val) {
            
$erg += $val;
        }
        return 
$erg;
    }
}
?>

<?php
class Multiplizierer extends Algorithmus {
    public function 
berechneZahlen($array) {
        
$erg 1;
        foreach(
$array as $val) {
            
$erg *= $val;
        }
        return 
$erg;
    }
}
?>

Zu obigem Code sollten keine Fragen entstehen, andernfalls sitzen die Grundlagen der OOP nicht. Es wurde bewusst auf eine saubere Fehlerbehandlung verzichtet, um das Beispiel nicht unübersichtlicher zu machen als nötig.

Bisher sollte noch alles wie bekannt ausschauen, wir haben ja auch noch nichts sonderlich spektakuläres erreicht. Jetzt kommt der Moment, in dem wir die Assoziation von Rechner zu Algorithmus legen. Zuerst wird eine neue Attributsvariable eingeführt, über die auf den derzeitigen Algorithmus zugegriffen werden kann. Der dazugehörige Setter erlaubt dann das Verändern der privaten Variable. Diese Funktion wird per "Type Hinting" dazu gezwungen, lediglich Instanzen der Algorithmenfamilie entgegenzunehmen - Typsicherheit ist ja immer was feines.
<?php
class Rechner {
    public 
$zahlen;
    private 
$algorithmus;
    public function 
__construct($array) {
        
$this->zahlen $array;
    }
    public function 
setAlgorithmus(Algorithmus $a) {
        
$this->algorithmus $a;
    }
}
?>

Somit ist die permanente Brücke geschlagen, Rechner kann nun Addierer und Multiplizierer, allgemeiner jede Kindklasse von Algorithmus aufnehmen.

Im letzten Implementierungsschritt fehlt natürlich noch die funktionale Beziehung, noch machen wir ja nichts schickes mit dem Algorithmus. Wir editieren also wieder Rechner und greifen schlichtweg auf die Schnittstelle des referenzierten Algorithmus zu.
<?php
class Rechner {
    public 
$zahlen;
    private 
$algorithmus;
    public function 
__construct($array) {
        
$this->zahlen $array;
    }
    public function 
setAlgorithmus(Algorithmus $a) {
        
$this->algorithmus $a;
    }
    public function 
berechneErgebnis() {
        return 
$this->algorithmus->berechneZahlen($this->zahlen);
    }
}
?>

Damit endet auch schon die Implementierung des Musters. Zur Reflektion nochmal eine kleine Zusammenfassung: erst haben wir den Kontext selbst geschrieben (der kann ja auch schon fertig vorliegen), dann eine Klasse von Algorithmen gebildet (wieder: diese können ja von mehreren Autoren stammen, wenn sie die Schnittstellen einhalten), zuguterletzt die Kontextklasse um die Assoziation zum Strategienbündel erweitert und funktional verbunden. Erneut sollten sich dem Leser die Vorzüge aufdrängen: die Strategien können sehr einfach individuell gewartet werden, ohne wirklich ihren Kontext zu kennen. Außerdem sind andere Kontexte denkbar, die nichts mit "Rechner" zu tun haben, aber dennoch die gleichen Strategien nutzen könnten.

Gesamtcode und Test:
Der Übersichtlichkeit wegen nochmal der gesamte Code, hier in einer Datei3:
<?php
abstract class Algorithmus {
    abstract public function 
berechneZahlen($array);
}

class 
Addierer extends Algorithmus {
    public function 
berechneZahlen($array) {
        
$erg 0;
        foreach(
$array as $val) {
            
$erg += $val;
        }
        return 
$erg;
    }
}

class 
Multiplizierer extends Algorithmus {
    public function 
berechneZahlen($array) {
        
$erg 1;
        foreach(
$array as $val) {
            
$erg *= $val;
        }
        return 
$erg;
    }
}

class 
Rechner {
    public 
$zahlen;
    private 
$algorithmus;
    public function 
__construct($array) {
        
$this->zahlen $array;
    }
    public function 
setAlgorithmus(Algorithmus $a) {
        
$this->algorithmus $a;
    }
    public function 
berechneErgebnis() {
        return 
$this->algorithmus->berechneZahlen($this->zahlen);
    }
}

$myRechner = new Rechner(array(5,8,11));

$myRechner->setAlgorithmus(new Addierer());
echo 
$myRechner->berechneErgebnis()."<br />";

$myRechner->setAlgorithmus(new Multiplizierer());
echo 
$myRechner->berechneErgebnis()."<br />";
?>

Spätestens beim kleinen Testcode sollte klar geworden sein, wie man das Muster dann benutzt. Es ist erneut darauf hinzuweisen, dass hier jegliche Fehlerbehandlung fehlt, die zu gutem Code nun mal dazu gehört.

Fazit:
Was haben wir gelernt? Die wichtigste Erkenntnis sollte sein, dass Entwurfsmuster trotz ihrer abstrakten Definition durchaus recht einfach auf Realweltbeispiele angewandt werden können und - wenn richtig erkannt und verwandt - viele Vorteile in Implementation und Wartung bringen können. Wie oben gesagt, sie funktionieren. Zweitens sollte der Leser die Idee hinter dem Muster "Strategie" (Strategy) verstanden haben und es auch selbstständig einsetzen können.

Wie geht es weiter? Zum tieferen Verständnis sollte sich der interessierte Leser selbst ein Beispiel überlegen und implementieren, oder im Idealfall eine bestehende Applikation (muss ja nicht PHP sein) auf das Vorkommen eines Strategy-Musters untersuchen. Dieses Tutorial sollte auch einen Anreiz geben, sich mal mit weniger direkt greifbaren Techniken zu beschäftigen und dem Entwurf einer Applikation mehr Aufmerksamkeit zu schenken. Ich werde das Thema Design Patterns (bzw. die bekanntesten Vertreter) gerne noch weiter beleuchten, sobald es die Zeit zulässt. In der Zwischenzeit beantworte ich natürlich gerne alle relevanten Fragen in den Kommentaren.

Bis dahin, Duddle.

 


1will ja jeder heutzutage, nicht wahr
2der Leser sollte sich selbst schnell überlegen, dass hier sehr viel mehr und/oder komplexere Berechnungen stehen könnten (Quersumme, Maximal-/Minimalfunktion, etc.)
3aufsplitten und per __autoload() besser organisieren sollte man selbst beherrschen


DVD-Werbung
Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Portrait von lexz
  • 28.06.2013 - 17:10

coole Sache! Vielen Dank

Portrait von alkunz
  • 14.10.2011 - 10:41

Vielen Dank für ein tolles Tutorial

Portrait von icehawk2
  • 17.01.2010 - 17:23

Kann mir mit dem ersten blick noch nicht so richtig etwas darunter vorstellen, aber die Erklärungen sind guut und verständlich. . . ICh werrde mir das im jedenfalle nochmal genauer ansehen.. . .

Portrait von Ditin
  • 20.08.2009 - 09:14

Danke, das Thema wurde sehr verständlich in den ersten Grundlagen erklärt. Eine Fortsetzung ist wünschenswert.

Portrait von progfrog
  • 13.03.2009 - 15:14

Super Idee! Diese Muster an sich, kannte ich zwar schon aus Informatik, aber vieleicht kommen ja noch andere. Finde es klasse, dass sich im Bereich der Programmiertechnischen Workshops auch mal mit solch konzeptionellen Problemen beschäftigt wird! :thumbsup: von mir!

Portrait von mfgleo
  • 12.03.2009 - 17:21

Nicht schlecht! Könnte aber noch ein wenig besser formatiert sein.

x
×
×