Abstrakte Fabrik

Problem

Objekte verwandter Klassen sollen erzeugt werden, so dass die zu verwendende Klasse erst zur Laufzeit festgelegt werden kann.

Motivation

Eine Aufgabe kann auf unterschiedliche Arten, beispielweise unter Verwendung verschiedener Protokolle oder Verfahren, durchgeführt werden. Die einzelnen Implementierungen sollen zur Laufzeit des Programms austauschbar sein.

Lösung

Die gemeinsame Funktionalität der einzelnen Implementierungen wird in einer abstrakten Basisklasse gekapselt. Die direkte Erzeugung von Objekten der Kindklassen dieser Basisklasse wird durch die Deklaration der entsprechenden Konstruktoren als protected oder private unterbunden. Für die Erzeugung von Objekten bietet die abstrakte Basisklasse eine statische Methode an, die anhand des übergebenen Parameters ein Objekt des gewünschten Typs erzeugt und zur Verfügung stellt.

Anwendungsbeispiele

Betrachten wir als Beispiel einen Online-Shop, der seinen Partnern ein Interface zum Produktkatalog zur Verfügung stellt. Der eine Partner wünscht eine Schnittstelle auf XML-RPC-Basis, ein weiterer würde gerne SOAP nutzen können, während ein dritter Geschäftspartner ein naives Protokoll auf der Basis von HTTP GET und POST verlangt.

Die Programmlogik ist bei den drei Varianten gleich, sie wird also zunächst in einer Basisklasse gekapselt. Von dieser Basisklasse leiten sich drei Klassen, je eine für XML-RPC, SOAP und HTTP GET / POST ab. Diese abgeleiteten Klassen implementieren den jeweiligen Kommunikationsmechanismus.

Bislang haben wir nur mit dem Konzept der Vererbung Coderedundanzen vermieden. Allerdings ist das Anlegen von Instanzen der drei Klassen aus der Applikation heraus noch nicht transparent, die Nutzung komplizierter als nötig, wie Beispiel 5.1 zeigt.

Beispiel 5.1: Verwendung der drei Klassen ohne abstrakte Fabrik

<?php
switch ($type) {
  case 'HTTP': {
    include_once 'partner_interface/http.php';
    $interface = new PartnerInterface_HTTP;
  }
  break;
 
  case 'SOAP': {
    include_once 'partner_interface/soap.php';
    $interface = new PartnerInterface_SOAP;
  }
  break;
 
  case 'XML-RPC': {
    include_once 'partner_interface/xml-rpc.php';
    $interface = new PartnerInterface_XMLRPC;
  }
  break;
}
?>


Nachdem die Auswahl des zu erzeugenden Objektes in der Methode factory() der Klasse PartnerInterface an zentraler Stelle gekapselt wurde, gestaltet sich die Erzeugung des passenden Objektes im Kontext des Programmes einfach und flexibel, wie Beispiel 5.2 zeigt.

Beispiel 5.2: Verwendung der drei Klassen mit abstrakter Fabrik

<?php
$interface = PartnerInterface::factory($type);
?>


Beispiel 5.3 zeigt die Implementierung einer abstrakten Fabrik in PHP, Beispiel 5.4 das Grundgerüst einer konkreten Kindklasse, für die die Fabrik Objekte erzeugen kann.

Beispiel 5.3: Die abstrakte Klasse PartnerInterface

<?php
abstract class PartnerInterface {
  protected function __construct() {}
 
  public static function factory($type) {
    $source = 'PartnerInterface/' . $type . '.php';
 
    if (@require_once($source)) {
      $class  = 'PartnerInterface_' . $type;
      $object = new $class;
 
      return $object;
    } else {
      throw new Exception(
        sprintf(
          'Konnte kein Objekt vom Typ %s erzeugen.',
          'PartnerInterface_' . $type
        )
      );
    }
  }
 
  public abstract function import($data);
  public abstract function export();
}
?>


Beispiel 5.4: Die konkrete Klasse PartnerInterface_HTTP

<?php
require_once 'PartnerInterface.php';
 
class PartnerInterface_HTTP extends PartnerInterface {
  public function import($data) {
    // ...
  }
 
  public function export() {
    // ...
  }
}
?>