Artikelformat

Dependency Injection (Teil 3)

Letzte Woche haben wir in der Dependency Injection Serie die Setter Injection gesehen. Und genau dieses Konzept wollen wir jetzt noch einen Schritt weiter treiben. Die heute vorgestellte Vorgehensweise entkoppelt die beiden Klassen dazu noch etwas mehr.

Interface Injection:
Das Konzept hierbei ist eigentlich recht einfach. Statt eine spezielle Klasse zu erfordern, nutzt man nun ein Interface. Das bedeutet für uns, dass unsere Klasse, die später über den Setter gesetzt wird, dieses Interface implementieren muss.

Das Interface definieren wir beispielsweise so:

interface DBAccess {
     public function query($sql);
}

Man kann sehen, dass eine Methode query vorhanden sein muss. Wenn wir uns an letzte Woche erinnern, so wissen wir, dass die Datenbank-Klasse genau diese Methode benutzt. Somit können wir uns schon vorstellen wie MyDatabase aussehen könnte:

class MyDatabase implements DBAccess {
     public function query($sql) {
          // send query to database
     }

     // some more code
}

Update (26.06.2011):
Die Example-Klasse ebenfalls ein Interface implementieren. Dieses gibt an, über welche Methode die Dependency gesetzt werden kann. Somit benötigen wir auch hier ein Interface. Dieses sieht dann bspw. so aus:

interface InjectDBAccess {
     public function setDatabase(DBAccess $db);
}

/Update

Nun müssen wir nun noch die Example-Klasse anpassen. Dazu wird der Setter so abgeändert, dass der Type Hint das Interface fordert:

class Example implements InjectDBAccess {
     private $db;
     public function __construct() {
          // init something
     }
     public function setDatabase(DBAccess $db) {
           $this->db = $db;
     }
     public function getSomething() {
          if ($this->db == null) {
               throw new Exception("database connection must not be null");
          }
          return secretFunction($this->db->query("Select * from test"));
     }
}

Da das Interface erfordert, dass die query-Methode vorhanden ist – sonst würde unsere MyDatabase-Klasse den Vertrag ja nicht erfüllen – können wir die Methode einfach aufrufen.

Vorteil:
Da man durch das Interface die Klassen entkoppelt, kann man die Klasse sehr schnell ersetzen, ohne die Type Hints anpassen zu müssen. Natürlich kann hier eine moderne IDE helfen, aber sowas ist schon eine Fehlerquelle.

Nachteil:
Man muss hier wie auch bei der Setter Injection die Klasse „von Hand“ setzen und die Nachteile aus Teil 2 sind somit immer noch präsent.

16 Kommentare

  1. Kleine Rechtschreibkorrektur:
    „Dazu wird der Setter so abgeändert_, dass_ der Type Hint das Interface fordert:“

    Ansonsten: Danke für den Einstieg in die Dependency Injection, jetzt kenne ich mich wieder ein wenig mehr aus.

    Antworten
  2. Klasse erklärt. Gibt es nichts zu meckern.

    @Norbert: Kleine Frage, dein DBAccess Interface fordert als Argument ein String Objekt. Ist das Richtig oder git es schon String als TypeHint?

    Antworten
    • Huch, natürlich hast du Recht. Strings gehen (noch) nicht. Ich habe den Source angepasst. Vermutlich bin ich zuviel in der Java-Welt unterwegs 😉

  3. Ich finde nicht, dass die Verwendung eines „Interfaces“ anstelle einer Klasse rechtfertigt, das gleich als dritte Variante von DI zu bezeichnen. Am Code der konsumierenden Klasse ändert sich absolut nichts.

    Das hier ist genauso „Setter-Injection“, wie das Beispiel in Teil 2. Die Methode und Funktionalität der konsumierenden Klasse heißt „setIrgendwas“, speichert das Objekt intern ab und checkt, ob es überhaupt verwendbar ist.

    Antworten
  4. sehe ich genauso wie Sven. Nix neues. Interfaces sparen einem die Problematik mit der Vererbungshierarchie. Deshalb sind Ifaces fast immer die bessere DI-Variante.

    Antworten
  5. @Sven: Grundsätzlich hast du recht. Interface Injection und Setter Injection sind gleich wenn es um die Implementierung geht (beide verwenden set Methoden). Der hauptsächliche Unterschied liegt in der Konfiguration. Setter Injection muss explizit konfiguriert werden, während hingegen Interface Injection vom DI Container erkannt wird und die nötige Dependency automatisch setzt. Vorausgesetzt natürlich das eingesetzte DI Framework unterstützt die Verwendung von Interface Injection.

    Der Fokus auf ein DI Framework bzw. den DI Container fehlte mir den Artikeln etwas. Natürlich kann man DI auch händisch machen, allerdings bringt das viel (Konfigurations-)Overhead mit sich, die Verwendung eines Frameworks hilft Resourcen zu sparen.

    Antworten
  6. @Stefan:

    Kann dein Argument nicht nachvollziehen. _Ich_ sehe _hier_ jedenfalls _keinen_ DI-Container mit irgendeiner Automatik und/oder Konfiguration.

    Und ich glaube auch nicht, dass DI-Frameworks nun ausgerechnet ein Interface zwingend erfordern, um funktionieren zu können.

    Nur nochmal in der Kurzform: Wir haben in den Artikel-Folgen 2 und 3 jeweils identisch: 1) Eine DB-Klasse, 2) eine konsumierende Klasse mit setDatabase()-Methode. Zusätzlich in 3 noch: 3) Ein Interface, welches von der DB-Klasse implementiert und anstelle der Klasse im Type-Hint von setDatabase() gefordert wird.

    Ich persönlich würde nicht auf die Idee kommen, mir ein Interface zu definieren, wenn ich keines brauche. Und brauchen tu ich eines, wenn ich Klassen mit unterschiedlichen Implementierungen derselben Methoden programmieren muss, mir also gemeinsamer Code in einer Elternklasse nichts hilft.

    Diese Überlegung treffe ich aber nicht wegen Dependency Injection, sondern weil während der Entwicklung diese Erfordernis auftritt (YAGNI-Prinzip). 🙂

    Ergo: Beides ist für mich Setter-Injection.

    Antworten
    • Interface Injection ist ein Pattern im IoC Kanon. Ich habe das weder erfunden, noch den Namen festgelegt. Schau doch einfach mal bei Google. Ob man das Prinzip als eigenen Artikel oder Randnotiz zu Setter Injection beschreibt ist mE Geschmackssache.

  7. Ich kann da @Norbert nur zustimmen. Interface Injection ist ein Pattern von IoC. Ob man da einen eigenen Artikel schreiben ist Geschmackssache.

    Aber ich finde es sehr gut das alles was IoC und Dependency Injection betrifft erklärt wird, nicht nur so leicht vorbeigeschrammt. Das Thema IoC und Depenedency Injection sind weitreichender als man denkt.

    Ich kann nur jeden, das Buch von Stephan Schmidt, „PHP Design Pattern“, ans Herz legen. Dort wird sehr gut beschrieben was IoC und Dependency Injectio ist.

    Antworten
  8. Was hat die Sache mit dem Interface mit DI zu tun? Ich kann zwar das mit der Nähe zu DI verstehen, aber das jetzt in einem Artikel, der sich um DI dreht – hm…

    Dann sollte man doch mal lieber auf die DI-Container eingehen. Damit ließe sich auch der herausgestellte Nachteil der benötigten Dependencies relativieren.

    Antworten
  9. @Norbert

    Gut, dass hier einige Leute auf ihren Ansichten beharren und Stichworte zu Quellen anführen, das zwingt ja geradezu, diesen Hinweisen auch nachzugehen und die eigene Position zu überprüfen.

    Und nach Prüfung einiger Quellen komme ich zur Erkenntnis: Norbert, du hast „Interface Injection“ nicht richtig verstanden, denn das, was du hier als solches beschreibst, ist was ganz anderes als das, was Martin Fowler (zitiert im Wikipedia-Artikel http://en.wikipedia.org/wiki/Dependency_injection#Types ) darunter versteht (sein Artikel siehe http://www.martinfowler.com/articles/injection.html#FormsOfDependencyInjection ).

    Der grundsätzliche Ansatz seiner Interface Injection: Das Objekt, welches die Dependencies injiziert bekommen soll, implementiert ein Interface, welches die Methode fordert, die als Setter dann die konkrete Implementierung der Dependency entgegennimmt. Jede Dependency der Klasse wird abgebildet durch die Implementierung des zugehörigen Interfaces der Dependency.

    Weil er für sein Beispiel zum einen Java verwendet, zum anderen diese Art von Injection nicht ohne ein drumherumliegendes Framework funktioniert (er benutzt Avalon), welches entsprechend konfiguriert werden muss, halte ich diesen Ansatz nicht für „einfach“ nach PHP übertragbar.

    Antworten
  10. @Sven: Gute Links, danke dafür. Im Link zu den Formen von DI wird doch ziemlich genau beschrieben was der Unterschied zwischen Setter Injection und Interface Injection ist und trifft IMHO das was auch ich versucht habe ausdrücken. Setter Injection passiert manuell, d.h. ich muss, in welcher Form auch immer, die Injection konfigurieren. Im Spring Beispiel aus dem Artikel heißt das konkret per XML Deklaration. Bei der Interface Injection registriere ich das Interface im DI Container, auch das ist aus dem Beispiel des Artikels erkennbar. Sonst muss ich nichts konfigurieren. D.h. Setter Injection geht nur auf Objekt-Ebene, ich muss für jedes Objekt explizit den set() Aufruf konfigurieren. Bei Interface Injection funktioniert das ganze implizit, ich definiere global „für alle Objekte die x implementieren indiziere das Objekt vom Typ y“.

    Während setter Injection manuell ohne DI Container erfolgen kann, funktioniert Interface Injection eben nur mit Hilfe einer globalen Instanz, dem DI Container.

    Antworten
  11. Na gut, ihr habt mich ein Stück weit überzeugt. Die Klasse, in die ein Objekt injiziert werden soll, wird auch ein spezielles Interface implementieren. Ich passe den Artikel entsprechend an und versuche die Änderung kenntlich zu machen.

    Antworten

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.