Composite Pattern

Das Compsosite Pattern (Kompositum - "Zusammengesetztes") ermöglicht es einen Aufruf aus Kommandos verschiedener Klassen zusammenzusetzen. Die Kommandos werden beim Kompositum registriert und bilden zusammen wiederum ein Kommando ab. Dadurch lässt sich eine sehr flexible Schachtelung erreichen.

Dazu zuerst das Interface für das Kompositum und der teilhabenden Klassen.

interface CommmandInterface {
 
  public function execute($message);
}

Alle Klassen, welche dieses Interface implementieren müssen also eine Methode mit dem Namen execute() besitzen. Diese bekommt in der Übergabe eine entsprechende Fehlermeldung.

Wenn Klassen nur eine Methode execute() und ansonsten nur die Logik zu deren Ausführung beinhalten, wird dies häufig auch als Command Pattern bezeichent.

In meinem konkreten Beispiel wird es darum gehen eine Fehlermeldung aus verschiedenen Ausgabearten zusammenzusetzen. Dazu im folgenden die konkreten Fehlerklassen. Alle Klassen sind nur Beispielhaft implementiert um die Funktionsweise des Composite Patterns zu veranschaulichen.

Error Log

Das schreiben eines Logeintrags. Der Konstrukor erwartet die zu schreibende Datei.

class ErrorLog implements CommandInterface {
 
  private $_file;
 
  public function __construct($file) {
    $this->_file = $file;
  }
 
  public function execute($message) {
    // $message in $file schreiben
  }
}

Error Mail

Das Senden einer Email. Der Konstruktor erwartet die Adresse der zu versendenden Email.

class ErrorMail implements CommandInterface {
 
  private $_address;
 
  public function __construct($address) {
    $this->_address = $address;
  }
 
  public function execute($message) {
    // $message per Mail an $address versenden
  }
}

Error Sms

Das Senden einer Sms. Der Konstruktor erwartet eine Handynummer.

class ErrorSms implements CommandInterface {
 
  private $_number;
 
  public function __construct($number) {
    $this->_number = $number;
  }
 
  public function execute($message) {
    // $message per Sms an $number versenden
  }
}

Error Output

Ausgabe der Nachricht.

class ErrorOutput implements CommandInterface {
 
  public function execute($message) {
    // z.B. echo $message
  }
}

Bis jetzt ist nichts weiter dabei. Jede Klasse enthält eine konkrete Implementierung ihrer Ausgabe. Jetzt gilt es die verschieden Fälle miteinander zu kombinieren. Dazu kommt die eigentlich Composite Klasse zum Einsatz.

Error Composite

Diese Klasse ermöglicht die Kombination der obigen Klassen zu einem Objekt für Fehlermeldungen.

class ErrorComposite implements CommandInterface {
 
  private $_errorTypes = array();
 
  public function register(CommandInterface $errorType) {
 
    $this->_errorTypes[] = $errorType;
  }
 
  public function execute($message) {
 
    foreach ($this->_errorTypes as $errorType) {
      $errorType->execute();
    }
  }
}

Die Methode register() enthält einen Type Hint auf das CommandInterface. Dadurch können nur Methoden beim Kompositum registriert werden, welche auch eine execute() Methode beinhalten.

Die execute() Methode des Kompositum führt alle execute() Methoden der bei sich registrierten Error Objekte aus.

So nun noch das ganze Im Einsatz. Es wird für das Beispiel ein normaler, sagen wir mal weniger kritischer Fehler angenommen.

$simpleError = new ErrorComposite();
 
// Logeintrag hinzufügen
$simpleError->register(new ErrorLog('error.log'));
 
// Mailversand hinzufügen
$simpleError->register(new ErrorMail('webmaster@example.com'));
 
// Ausgabe hinzufügen
$simpleError->register(new ErrorOutput());
 
// den Fehler loggen/mailen/ausgeben
$simpleError->execute('Es ist ein unkritischer Fehler aufgetreten.');

Das war ja bis hierher nicht schwer - keine Angst, schwerer wirds auch nicht. Aber das war noch nicht alles. Da die Konstuktion sehr flexibel ist, werden wir jetzt noch einen schwerwiegenden Fehler anlegen, bei dem der Admin zusätzlich mit einer SMS benachrichtigt wird.

$criticalError = new ErrorComposite();
 
// SMS an Admin hinzufügen
$criticalError->register(new ErrorSms(0190123456789));
 
// Email an Admin hinzufügen
$criticalError->register(new ErrorMail('admin@example.com');
 
// Handling des einfachen Fehlers hinzufügen
$criticalError->register($simpleError);
 
// den Fehler loggen,ausgeben,mailen (admin & webmaster),sms (admin)
$criticalError->execute('All your base are belong to us!');

Der kritische Fehler beeinhaltet dadurch das Systemverhalten eines einfachen Fehlers und ergänzt es durch die Information an den Admins als Mail und Sms.

Diese Prinzip ermöglicht es das Composite Pattern in einer Baumstruktur sehr komplex zu gestalten und alle registrierten Objekte mit einem Aufruf auszuführen.

Composite - Kategorie, Subkategorie

Sehr schöner Artikel, aber leider finde ich das Beispiel über eines Debuggers / Benachrichtigungssystem sehr oft im Internet.

Ich würde gerne eine Webseite reallisieren, wobei die einzelnen Kategorien quasi die Komposition sind und Artikel die Blätter des Baums. So weit so gut - klappt auch so weit das ich nun im PHP Code die einzelnen Kategorien und Artikel anlegen kann, aber wie speicher diese nun persistent in einer relationalen Datenbank? - Über Hinweise würde ich mich freuen.

Ein Gast (E-Mail bitte nicht publizieren) ;-)

Baumstruktur in Datenbank ablegen

Grüße! Erstmal danke für den Beitrag, da freut sich der Autor =) Zu deiner Problematik fällt mir spontan das Thema "Nested Sets" ein. Mit diesem Prinzip können auch sehr komplexe Baumstrukturen in einer Datenbank abgelegt, ausgelesen und bearbeitet werden. Ein Artikel, welcher mir damals weiter half findest du unter http://www.klempert.de/nested_sets/artikel. Aber auch bei Google solltest du eine Menge darüber finden.
Weiterhin könnte auch das "Node" Prinzip des CMS Drupal ein paar gute Gedanken liefern. Jeder Eintrag der Seite (egal ob Artikel, Bild usw.) wird als Knoten in der Datenbank abgelegt. Mit Hilfe des im Core enthaltenem Moduls "Taxonomy" kann eine Baumstruktur/Kategorisierung erreicht werden.
Ich hoffe dir damit einen Wink in die richtige Richtung gegeben zu haben.

Gruß,
Benjamin