Artikelformat

Das Factory-Pattern

Heute gibt es wieder einen Beitrag aus der Pattern-Kiste. Das Factory-Pattern ist ein netter Helfer, wenn man eine Klasse erzeugen möchte, den Konstruktor aber nicht so direkt nutzen kann oder will. Man erstellt also eine zweite Klasse, die Factory. In dieser Klasse stellt man Methoden bereit, die ein Objekt der ersten Klasse, die man ja eigentlich nutzen will, zurückliefert. Die Methoden können auch statisch sein. Dagegen spricht mE nichts. Jetzt stellt sich die Frage, wann man ein solches Vorgehen nutzen soll.

Ein Beispiel, dass ich hierfür gerne heranziehe, ist der Zugriff auf eine Datenbank. Angenommen man nutzt keinen OR-Mapper, dann ist eine solche Factory eine nette Variante, um Objekte aus der Datenbank auszulesen. In der Factory definiert man sich Methoden, die verschiedene SQL-Queries abbilden. Zum Beispiel getAll() oder findByName(). In diesen Methoden realisiert man den Datenbankzugriff wie man dies herkömmlich kennt und baut die Ergebnisse in Data Access Objects um, die durchaus vollkommen primitiv und frei jeglicher Logik sind bzw. sein dürfen. getAll() würde ein Array von DAOs zurückliefern und dieses kann man dann in einem Templatesystem weiterverwenden.

Der Vorteil ist nun, dass man den DB Zugriff in einer Klasse (nämlich der Factory) kapseln kann. Die restliche Anwendung muss nichts von der Datenbank wissen. In einem privaten Projekt habe ich so 2 Factories, die mir den Zugang zur Datenbank erlauben. Ich bin der Ansicht, dass bei komplexeren Datenbanken-Schemata es natürlich sinn macht Doctrine oder Propel einzusetzen, da man ab einem bestimmten Punkt seinen eigenen OR-Mapper erfindet und das ist nicht sehr pragmatisch.

Natürlich gibt es bei der Factory auch Nachteile. Dazu gehört bspw. die Tatsache, dass man um ein Objekt zu erzeugen 2 Klassen benötigt. Einerseits die Factory und andererseits die Klasse, die von der Factory geliefert werden soll. In einer kleinen überschaubaren Anwendung ist dieser Overhead vertretbar.

Nun ein kleines Beispiel. Hier gibt es eine Klasse Car und eine CarFactory diese kann rote und blaue Autos erzeugen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Car {
 
    private $color = null;
 
    public function __construct() {
        $this->color = "white";
    }
 
    public function setColor($color) {
        $this->color = $color;
    }
 
    public function getColor() {
        return $this->color;
    }
}

Die Klasse ist sehr übersichtlich. Man kann eine Farbe setzen und diese auslesen. Der Standardwert ist weiß. Die Factory ist ähnlich komplex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CarFactory {
 
    private function __construct() {
 
    }
 
    public static function getBlueCar() {
        return self::getCar("blue");
    }
 
    public static function getRedCar() {
        return self::getCar("red");
    }
 
    private static function getCar($color) {
        $car = new Car();
        $car->setColor($color);
        return $car;
    }
}

Hier gibt es eine Methode für rote Autos, eine für blaue und damit man den Code nicht dupliziert eine private Methode, die die eigentliche Erzeugung durchführt. Nutzt man diesen Code, dann sieht das zum Beispiel so aus:

1
2
3
4
5
$car1 = CarFactory::getBlueCar();
echo "Car 1 is ".$car1->getColor() ."\n";
 
$car2 = CarFactory::getRedCar();
echo "Car 2 is ".$car2->getColor() ."\n";

Die Ausgabe sieht so aus, wie man es erwarten würde:

1
2
Car 1 is blue
Car 2 is red

Und jetzt kann man die CarFactory erweitern, sodass auch Automarken mit ihren Feinheiten berücksichtigt werden oder ähnliches.

7 Kommentare

  1. Ich denke nicht, dass der Einsatz von Factories einen bedenklichen Overhead darstellt und das gerade in komplexeren Anwendung das Pattern Anwendung finden sollte. Denn eine Factory kann mehr als nur beliebig konfigurierte Objekte zurückgeben.

    Ein Beispiel: In einer Anwendung wird an ca. 2000 Stellen ein Car-Objekt mit „new“ erzeugt. Nun ergibt es sich, dass in Zukunft die Klasse „SpezialCar“ verwendet werden muss.

    Jetzt könnte man hergehen und alle new ersetzen oder einfach die Klasse umbenennen, beides ist nicht schön. Oder einfach von Anfang an mit Factories arbeiten und dann mit getCar() ein SpezialCar statt Car zurückgeben.

    Durch die Verwendung des Patterns werden Abhängigkeiten gelöst und der Code sollte übersichtlicher werden.

    Antworten
    • In dem von dir beschriebenen Fall, kann man, wenn man sich die Schreibarbeit sparen möchte, Car auch von SpezialCar ableiten und den Konstruktor – falls verschieden – entsprechend anpassen. Ich finde, hier sollte die IDE dem Entwickler unter die Arme greifen und entsprechende Refactoring Möglichkeiten anbieten. Natürlich ist das bei einer dynamischen Sprache mit schwacher Typisierung nicht so einfach.

  2. Sven Rautenberg

    18/05/2010 @ 16:19

    Das Factory-Pattern ist vor allem dann angesagt, wenn ein Objekt abhängig ist von anderen Objekten, die vorher erstellt und in das eigentliche Objekt integriert werden müssen. Stichwort Dependency Injection.

    Aus meiner Sicht ist es, gerade wenn man testbaren Code schreiben will, ein sehr guter Ansatz, das Zusammensetzen von fertig nutzbaren Objekt-Gruppen in einer Factory vorzunehmen, und sich andernorts um andere Aspekte zu kümmern. Der Konstruktor des Objekts beispielsweise erwartet ein Datenbankobjekt – das kann man im Unit-Test mocken, in der Benutzung will man sich damit aber nicht herumschlagen.

    Antworten
    • Testbarer Code ist auf jeden Fall ein Vorteil, den ich nicht bedacht hatte. Dabei nutze ich bei Unittests Factories relativ häufig.

  3. Danke für den schönen Artikel.

    Mir ist genau das passiert, dass ich mir einen OR-Mapper selber programmiert und dabei das Factory Pattern eingesetzt habe.

    Inzwischen kenne ich ORM und Factory und ich nerve mich darüber, warum ich soviel Mühe und Zeit investiert habe, um ein Problem zu lösen, das schon längstens gelöst ist 🙂

    Antworten
  4. Hi, ich bin bzgl. OOP echt ein Anfänger und habe eine Frage zu dem Code. Und zwar rufst du diese Funktion auf:

    $car1 = CarFactory::getBlueCar();

    dort wird

    return self::getCar(„blue“);

    zurückgegeben. Wäre es in diesem Fall nicht auch möglich „self::getCar(„blue“);“ direkt aufzurufen, ohne den Umweg über getBlueCar?

    Antworten

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.