Artikelformat

Dependency Injection (Teil 2)

Letzte Woche gab es den ersten Teil der DI-Beitragsreihe. Es wurde das Konzept der Constructor Injection vorgestellt. Heute wollen wir einen kleinen Schritt weiter in Richtung DI-Container gehen. Dafür wird ein weiteres Konzept vorgestellt, dass eine Alternative zur Constructor Injection ist.

Das letzte Beispiel sah so aus:

class Example {
     private $db;
     public function __construct(MyDatabase $db) {
          $this->db = $db;
     }
     public function getSomething() {
          return secretFunction($this->db->query("Select * from test"));
     }
}

Setter Injection:
Bei diesem Konzept kann man den Konstruktor Aufruf säubern, da nicht alle Abhängigkeiten beim Erzeugen des Objekts vorhanden sein müssen. Diese können nachträglich gesetzt werden. Das Beispiel sieht dann umgebaut so aus:

class Example {
     private $db;

     public function __construct(MyDatabase $db) {
          // init something
     }

     public function setDatabase(MyDatabase $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"));
     }
}

Nachteile:
Da man die Initialisierung des Objekts und das Setzen der Abhängigkeiten entzerren kann, besteht auch die Gefahr, dass man einen Methodenaufruf nutzt, der noch nicht funktional ist. Somit muss man sich gegen diesen Fall absichern.
Wenn viele Abhängigkeiten existieren muss man eine ganze Reihe Setter-Aufrufe aneinander reihen. Die Abhängigkeitsanzahl wird aber dadurch auch nicht reduziert.

Die Vorteile, die man schon aus der Constructor Injection kennt, hat man hier natürlich auch. Das Unittest-Mock Beispiel greift somit wieder.

4 Kommentare

  1. Also ich plädiere ja für mehr Klarheit in der Sprache

    > Da man die Initialisierung des Objekts und das setzen der Abhängigkeiten entzerren kann besteht auch die Gefahr, dass man einen Methodenaufruf nutzt, der noch nicht funktional ist. Somit muss man sich gegen diesen Fall absichern.

    Öhm? Soll das bedeuten, dass man vor dem Aufruf von getSomething() das init vergessen könnte? Verstehe den Satz leider nicht.

    Antworten
  2. Na so kompliziert ist der Satz jetzt auch wieder nicht. Aber ich versuche es mal anders zu erklären:
    Nach dem Aufruf des Konstruktors sind in dem Objekt nicht alle Abhängigkeiten vorhanden, die es benötigt. Man muss in dem obigen Beispiel noch setDatabase aufrufen. Dies muss geschehen bevor man getSomething aufruft, da ansonsten der Methodenaufruf fehl schlagen muss.
    Wenn du das Setzen der Abhängigkeiten mit init meinst, dann ja und du hast alles verstanden.

    Antworten
  3. Der neue Konstruktor sollte imho so aussehen:

    public function __construct(MyDatabase $db) {
    $this->setDatabase($db);
    // init something more
    }

    Antworten
  4. Christian Weiss

    18/07/2011 @ 12:04

    Im Sinne von Clean Code plädiere ich für injectSomething() anstelle von setSomething() – da es klarer ausdrückt, was hier passiert.

    Über die Methodennamen und die „implements“ Angaben der Klasse werden, dann schon beim Lesen, die Abhängigkeiten deutlicher als mit setSomething().

    Ich würde mich freuen, wenn sich diese Konvention durchsetzt.

    Antworten

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.