• PHP-Entwurfsmuster: Factory Method

    von am 26. April 2010
    Dieser Artikel wurde auf Wunsch der phphatesme Leser verfasst und wurde über die Ideenschmiede eingereicht. Falls du auch eine Idee für einen Artikel hast, dann füge sie doch einfach hinzu.

    Auf heutigen Tag widmen wir uns mal wieder einem Entwurfsmuster. Um ganz genau zu seine einem Erzeugungsmuster, also eine Muster, dass sich darum kümmern Klassen zu instanzieren. So oft “Muster” in einem so kurzen Abschnitt … Respekt! Natürlich kann man jedes Objekt mit “new” erstellen und fertig. Aber das wäre uns ja zu einfach. Naja vielleicht nicht zu einfach, man sollte weiterhin new verwenden, wenn es Sinn macht. Manchmal gibt es aber saubere Methoden.

    Nehmen wir uns einfach mal mein Lieblingsbeispiel: den Logger. Wir haben eine Anwendung geschrieben, die einen Logger benötigt, um alle wichtigen Dinge in eine Textdatei zu schreiben. Sollte ja jedem vertraut klingen. Um jetzt nicht wild mit perfekten Beispielen rumzuwerfen nehmen wir einfach was sehr simples.

    <?php
    class FileLogger implements Logger
    {
      // ... 
    
      public function log( $message )
      {
        // ...
      }
    }
    
    $logger = new FileLogger( 'log.txt' );
    
    // ...
    
    $logger->log( 'Something important has happend' );
    
    // ...
    
    ?>

    Bis jetzt scheint der Code sauber. Denn wir hantieren mit Interfaces und nutzen den Logger auch wie es sein soll. Das Problem ist nur, dass wir vielleicht den Logger irgendwann austauschen wollen. In Textfiles loggen ist ja so 80er. Wir speichern jetzt alles in eine Datenbank. Eigentlich wäre es doch aber toll, wenn man sowas als eine Art Konfiguration sehen und nicht als Codeanpassung. Vielleicht will ich ja auch mal wieder zurückschwenken oder im Dev-System auf einen EchoLogger zurückgreifen. Das kann in mit einem solchen Code grad mal vergessen.

    Wir müssen also verhindern gegen eine konkrete Implementierung zu entwickeln, sondern müssen das ein wenig abstrahieren. Natürlich hilft da das Entwurfsmuster, mit dem wir heute ein wenig rumspielen. Wie vorhin schon angedeutet, werden wir nicht mit new hantieren, sondern die Instanz über eine Fabrik-Methode erstellen. Ich zeig am besten erst mal, wie es aussehen könnte und dann erkläre ich, warum es so aussieht.

    <?php
    
    public function loggerFactory( $loggerType, $params )
    {
      switch $loggerType
      {
        case 'text':
          // ein wenig defensives programmieren hier ...
          $logger = new TextLogger( $params['filename' );
         break;
         case 'db':
           // ein wenig defensives programmieren hier ...
           $logger = new DbLogger( $params['database_name' /* ... */   );
           break;
         default:
           throw new Exception( 'Logger not found' );
       }
      return $logger;
    }
    

    Jetzt können wir uns ganz einfach einen Logger bauen:

    <?php
      $config = array( 'logger_type' => 'text', 'filename' => 'log.txt' );
      $logger = loggerFactory( $config['logger_type'], $config );
      /* @var $logger Logger */
    ?>
    
    

    Woher denn jetzt genau diese Config-Datei kommt, da wollen wir gar nicht drauf eingehen.Wichtig ist nur, dass wir jetzt Konfigurieren und nicht mehr eine konkrete Klasse instanzieren. Unser Code ist jetzt also vorbereitet, diese Konfiguration außerhalb des Source-Codes zu betreiben und damit haben wir gewonnen. Meiner Meinung nach haben wir sogar doppelt gewonnen, denn wir haben uns auch noch dazu genötigt, mit Interfaces zu arbeiten, was ja leider in PHP nicht immer an der Tagesordnung ist.

    Nils Langner

    Auch wenn Ihr es mir nicht glauben werdet, aber ich habe nichts gegen PHP. Ich rege mich einfach nur gerne auf. Ok so schlimm ist es auch nicht. Eigentlich wollte ich schon immer einen Blog haben und da ...

    Zum Profil von Nils Langner

    19 Kommentare »


    • Nils Langner
      am 26. April 2010 um 09:40 Uhr

      Dem ist wohl nichts mehr hinzuzufügen.


    • T-Moe
      am 26. April 2010 um 10:28 Uhr

      Hm.. Ich muss gestehen, dass ich bis heute zwar das Prinzip hinter dem Factory Pattern, aber nicht seinen Sinn verstanden habe. Irgendwie kam mir nie in den Sinn, dass sich dieses Pattern mit der Konfiguration verbinden lässt und somit zum Beispiel dieses Problem der verschiedenen Logger in den Environments vereinfacht. Ich glaube, mein Bootstrap wird dadurch kürzer^^

      Eine Frage habe ich allerdings: Spricht etwas dagegen, die Factory annehmen zu lassen, dass Logger immer Logger_File o.ä. heißen? Wenn man einen Autoloader benutzt muss man schließlich somit nie den Code der Factory anpassen sondern kann einfach eine neue Datei anlegen. Oder gibt es etwas, was dagegen spricht?


    • tobias
      am 26. April 2010 um 10:35 Uhr

      *mit leids kommentar* (nach twitter aufruf) ;-)

      Klingt interessant, leider ist das Logger Beispiel nach dem Buch von Stephan Schmidt schon ziemlich … (wie nenn ich es mal?) “ausgelutscht”. Aber ansonsten nochmals nett aufbereitet.

      Müsste die Zeile:
      $logger = loggerFactory( $config['logger_type'], $config );

      nicht

      $logger = loggerFactory( $config['logger_type'], $config['filename']);

      heißen? Und in der Zeile
      $logger = new TextLogger( $params['filename' );

      fehlt eine "]” (eckige Klammer zu). ;-)


    • Stephan Hochdoerfer
      am 26. April 2010 um 10:37 Uhr

      Factories sind durchaus schön haben aber den Nachteil dass der Programmcode eine Abhängigkeit zur Factory erhält und man somit innerhalb eines Testcases keine Möglichkeit hat den Rückgabewert der Factory zu beeinflussen.

      Was aber nicht heißt dass Factories generell böse sind, man muss nur schauen wie man das Pattern am sinnvollsten im eigenen Code einsetzt :)


    • Marek Luthardt
      am 26. April 2010 um 10:41 Uhr

      Sehr schönes, aber auch irgendwie klassisches Beispiel für Factories. Setzt man hingegen bestimmte Klassen hauptsächlich zur Gewährleistung der Typsicherheit ein (Stichwort Type Hints), so kann man mittels einer Factory sehr schön aus sehr verschieden zusammengesetzten Arrays die passenden Objekte erzeugen. Array in die Factory hineinkippen, passendes Objekt kommt umgehend zurück. Man hat dann die Logik, wie aus unstrukturierten Daten “richtige” Objekte werden, in einer einzigen Stelle zusammengefasst.


    • Sven
      am 26. April 2010 um 10:47 Uhr

      Genau. Sinnvolle Einsatzgebiete sind vor allem:

      - Caches
      - Logger
      - Datenbankenmanager
      - Sessionmanager
      - Configmanager

      Hat jemand noch weitere Vorschläge wo das Pattern Sinn macht? Oder wo es vielleicht gar nicht passt?


    • Marcel
      am 26. April 2010 um 10:57 Uhr

      Sinn macht eine Factory eigentlich generell immer dann, wenn während der Laufzeit ein Verhalten festgelegt wird. Also immer dann, wenn ich noch gar nicht weiß, wie etwas ablaufen wird.

      Ich habe es vor Kurzem für mich neu entdeckt und mich auch neu begeistert bei einem Projekt, welches Daten an verschiedene Server von Unternehmen sendet. Die entsprechenden SOAP Schnittstellen waren alle Grundverschieden und nachdem der User einen Button geklickt hat, musste das Programm eben entscheiden, welche Schnittstelle genutzt werden soll. Über eine Factory war das sehr schön komfortabel zu lösen.


    • Stephan Hochdoerfer
      am 26. April 2010 um 11:30 Uhr

      Die Beispiele sind ja alle recht low-level (Logger, DB, Session). Factories machen natürlich auch innerhalb der Business-Logik Sinn. Beispielsweise könnte ich mir vorstellen das man im Context eines Webshops Factories für Zahlweisen und Versandarten baut. Die entsprechenden Implementierungen wissen wie mit den Daten umzugehen sind (z.B. Kommunikation mit Payment-Dienstleister) und ev. auch welche Felder im Frontend angezeigt werden sollen (Bankdaten Kreditkartendaten).


    • Tom
      am 26. April 2010 um 12:14 Uhr

      Dem ist kaum was hinzuzufügen. Eventuell noch: natürlich bietet es sich an im Sinne der Typsicherheit darauf zu achten, dass die zurückgelieferten Objekte alle eine gemeinsame (abstrakte) Basisklasse haben und/oder ein gemeinsames Interface implementieren. Je nachdem was passender ist.

      Neben der oben beschriebenen “Factory Method” sollte man aber vielleicht auch einen Blick auf das größere Pattern, die “Abstract Factory” werfen. Diese bietet vmtl. auch mehr Stoff zur Diskussion.

      Siehe Wikipedia: http://de.wikipedia.org/wiki/Abstrakte_Fabrik


    • Christian
      am 26. April 2010 um 12:26 Uhr

      @Sven Input-/Output-Plugins
      Beispiele:
      - Es soll etwas von Format-A in Format-B umgewandelt werden.
      - Ein API soll Antworten in verschiedenen Formaten ausgeben.
      - Eine PHP-Applikation soll via HTTP, STDIN oder was auch immer angesprochen werden können und ihren Output als HTML, XML, reinen Text oder sonstiges auf der Konsole ausgeben, an einen Browser via HTTP zurückgeben oder eine Datei auf Filesystemebene erzeugen und ablegen.


    • ragtek
      am 26. April 2010 um 13:41 Uhr

      Ich habe auch immer wieder davon gelesen und versucht es umzusetzen, nur konnte ich wirklich keinen Vorteil für mich finden.

      zB Logger Beispiel
      Was spricht hier gegen eine Funktion, die das Objekt zurück gibt?
      zB:

      function makeLogger($type, $args)
      {
      switch_case $type
      {
      case ‘txt’:
      $logger = new TextLogger($args);
      brake;
      case ‘db’:
      $logger = new DbLogger($args);
      brake;
      }
      return $logger;
      }

      $applicationLogger = makeLogger(‘db’);
      Bei Klassen benötigt man “bisschen” mehr Code und da ich ziemlich faul bin, versuch ich mir das zu sparen.


    • Christian
      am 26. April 2010 um 17:48 Uhr

      @ragtek Und wo willst du die Funktion unterbringen?
      Sowas gehört einfach in eine Klasse. Alleine schon wegen der Testbarkeit. Sonst müsstest du die Datei, die die (ober auch noch weitere solcher Funktionen) enthält on-demand immer wieder inkludieren. Macht man einfach nicht. :)

      Aber ehrlich gesagt verstehe ich dein Problem nicht. Wo ist der große Aufwand, wenn man statt der Funktion in einem losen PHP-Skript diese Funktion als Methode einer eigenständigen Klasse implementiert? Ist doch im Aufwand keinerlei Unterschied. Bringt nur Vorteile.


    • ragtek
      am 26. April 2010 um 18:34 Uhr

      Naja, ehrlichgesagt mache ich es genauso.
      Sobald ich die brauche, binde ich sie per require_once ein.


    • Julian
      am 26. April 2010 um 19:50 Uhr

      Um ehrlich zu sein, wundert es mich, dass im Zusammenhang mit dem Factory Pattern noch garnicht Dependency Injection angesprochen wurde.

      In Kombination kommt man damit sehr weit und schafft eine sehr gut wiederverwendbare API bei sauberer Anwendung.

      Ich hatte dazu vor kurzem eine Notiz an mich selbst geschrieben (Factory + Dependency Injection verstecken sich im Thema, sind aber letztendlich der Kern): http://julian.pustkuchen.com/registry-statt-singleton


    • MBa
      am 27. April 2010 um 12:06 Uhr

      Naja, Datenbank wurde ja schon öfters erwähnt, aber ich habe mal etwas gemacht (für Reporting/ETL), was eine Datenquelle mittels Factory erzeugt hatte.
      Dies konnte alles mögliche an 2-Dimensionalen Daten (Tabellenstruktur) sein:
      - SQL-Query
      - CSV
      - definiertes XML (auch Soap)
      - Log-Dateien
      - aufbereitete Objecte einer Masteraplication

      Ich mag dieses Muster einfach, nur die Schnittstellen müssen klar sein. ;)

      @ragtec: Manchmal braucht man einfach mehr wie eine Methode, denke dann wird es mit einfachen Funktionen unübersichtlich. Klar kann man pro includeter Datei dann mehrere Funktionen definieren. Aber schön ist was anderes. Jede Funktion braucht dann ein ‘Prefix’ (zB.: $sFunctionName.’_execute()’), damit unterschiedliche Dateien gleichzeitig genutzt werden können. Bei Klassen heißt nur die Klasse anders, die Methoden haben identische Namen.
      Und wenn die einzelnen ‘nachgeladenen’ Klassen sich von einer abstrakten Klasse ableiten, steht jeder Klasse weitere Funktionalität zu Verfügung (zB: Validierung), die man sonst immer irgendwie anders bekommen muss.
      Ein weiterer Vorteil ist, dass es sich um ein Pattern handelt und andere, die das kennen sich wesentlich schneller in den Code einarbeiten.


    • Flyingmana
      am 27. April 2010 um 14:20 Uhr

      Also ich weis nicht, das Factory Pattern begeistert mich nicht wirklich.

      Mal ganz davon abgesehen, dass man sich dadurch eine Abhängigkeit schafft, die beim Testen durchaus Probleme Verursachen kann.

      Man macht sich damit komplett Abhängig von dieser einen Klasse, die man weder austauschen noch erweitern kann, ohne den Quellcode dieser Klasse bzw. des ganzen Projektes zu verändern.

      Ich bin der Meinung, dass selbst ein Logger per Dependency Injection eingefügt werden sollte.

      Und wenn man diesen Logger mittels einer Konfiguration auswählt, macht man halt $logger = new $Logger_from_config;


    • ragtek
      am 28. April 2010 um 11:28 Uhr

      OK danke für die Antworten:)

      Mir fehlt hier dann vermutlich einfach die Routine, da ich (leider) nicht mit anderen zusammenarbeite und sich dadurch wohl auch niemand mit meinem Code auseinand setzen muss…..

      Gibt es sonst noch irgendwelche Beispiele ausser logger wo man sowas einsetzen kann?
      Im PHP Design Pattern Buch ist soweit ich mich erinnere auch nur das logger Bsp. drinn.


    • ragtek
      am 28. April 2010 um 11:30 Uhr

      Und noch eine Frage: Wo würde man dann

      <?php

      public function loggerFactory( $loggerType, $params )
      {
      switch $loggerType
      {
      case 'text':
      // ein wenig defensives programmieren hier …
      $logger = new TextLogger( $params['filename' );
      break;
      case 'db':
      // ein wenig defensives programmieren hier …
      $logger = new DbLogger( $params['database_name' /* … */ );
      break;
      default:
      throw new Exception( 'Logger not found' );
      }
      return $logger;
      }
      platzieren?


    • MBa
      am 28. April 2010 um 20:35 Uhr

      @ragtek:
      Lies mal den Eintrag von Stephan Hochdoerfer durch.
      Das mit den Versand/Paymentmodulen in Shops ist wirklich gang und gebe.

    RSS Feed für Kommentare zu diesem Artikel. TrackBack URL

    Hinterlasse einen Kommentar

    Werbung
    PHP Magazin
    Ausgabe 02/2010

    Dieses Mal mit Artikeln zu den Themen OpenSocial und Apache Shindig, Graphentheorie, Smarty3

    t3n
    Ausgabe 19

    Social Media (R)evolution. Weitere Themen sind noSQL, Crowdsourcing ...

    PHP Journal
    Ausgabe 2/2010

    PHP & Windows optimal nutzen, die besten PHP-CMS im Überblick, Google-API mit Zend Framework nutzen.

    Wir wurden schon öfters gefragt, ob man uns nicht irgendwie unterstützen kann. Die Antwort war immer einfach: Klar! Am einfachsten ist es eure nächsten Einkäufe bei Amazon über unsere Link abzuwickeln. Damit würdet ihr uns schon sehr helfen. Über Co-Autoren freuen wir uns aber noch mehr.