Abhängigkeitsinjektion: Feldinjektion vs. Konstruktorinjektion?


61

Ich weiß, dass dies eine heiße Debatte ist und dass sich die Meinungen über die Best-Approach-Praxis im Laufe der Zeit ändern.

Ich habe in meinen Kursen ausschließlich Feldinjektionen verwendet, bis ich in verschiedenen Blogs ( z. B . : petrikainulainen und schauderhaft und fowler ) über die Vorteile der Konstruktorinjektion nachgelesen habe . Seitdem habe ich meine Methoden geändert, um Konstruktorinjektion für erforderliche Abhängigkeiten und Setterinjektion für optionale Abhängigkeiten zu verwenden.

Allerdings habe ich vor kurzem in eine Debatte bekommen mit dem Autor von JMockit - ein Mockframework - in dem er Konstruktor & Setter Injektion als schlechte Praxis sieht und zeigt an, dass die JEE Gemeinschaft mit ihm übereinstimmt.

Gibt es in der heutigen Welt eine bevorzugte Art der Injektion? Wird Feldinjektion bevorzugt?

Nachdem ich in den letzten Jahren von Feldinjektion zu Konstruktorinjektion gewechselt bin, finde ich die Verwendung viel klarer, aber ich frage mich, ob ich meinen Standpunkt noch einmal überprüfen sollte. Der Autor von JMockit (Rogério Liesenfeld) ist mit DI klar vertraut, daher fühle ich mich verpflichtet, meinen Ansatz zu überprüfen, da er sich so stark gegen die Injektion von Konstrukteuren / Setzern fühlt.



2
Wenn Sie keine Konstruktor- oder Setter-Injection verwenden dürfen, wie sollen Sie dann Ihre Abhängigkeiten an die Klasse übergeben? Vielleicht sollten Sie einen Link zu der Debatte setzen, es sei denn, Ihr Gespräch mit dem Mockit war privat.
Robert Harvey

2
@DavidArno: In einer unveränderlichen Klasse wird der Status einmal im Konstruktor festgelegt.
Robert Harvey

1
@Davkd, was Sie beschreiben, ist das anämische Domänenmodell. Es hat sicherlich Befürworter, ist aber nicht mehr wirklich objektorientiert; wo Daten und die Funktionen, die auf diese Daten wirken, zusammenleben.
Richard Tingle

3
@David An der funktionalen Programmierung ist nichts auszusetzen. Aber Java ist grundsätzlich objektorientiert und Sie werden ständig dagegen ankämpfen und am Ende das anämische Domänenmodell haben, wenn Sie nicht sehr vorsichtig sind. Es ist nichts Falsches an funktionsorientiertem Code, aber es sollte ein geeignetes Tool dafür verwendet werden, was Java nicht ist (obwohl Lambdas Elemente der funktionsbasierten Programmierung hinzugefügt haben)
Richard Tingle

Antworten:


48

Die Feldinjektion ist für meinen Geschmack ein bisschen zu "gruselig in der Ferne" .

Betrachten Sie das Beispiel, das Sie in Ihrem Google Groups-Beitrag angegeben haben:

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

Sie sagen also im Grunde: "Ich habe diese Klasse mit privatem Status, an die ich @injectableAnmerkungen angehängt habe , was bedeutet, dass der Status automatisch von einem Agenten von außen ausgefüllt werden kann, obwohl mein Status als privat deklariert wurde . "

Ich verstehe die Gründe dafür. Es ist ein Versuch, einen Großteil der Zeremonie zu vermeiden, die der ordnungsgemäßen Einrichtung einer Klasse innewohnt. Grundsätzlich heißt es: "Ich bin es leid, all dieses Boilerplate zu schreiben, also werde ich einfach meinen gesamten Zustand mit Anmerkungen versehen und den DI-Container sich darum kümmern lassen, es für mich einzustellen."

Es ist eine vollkommen gültige Sichtweise. Es ist jedoch auch eine Problemumgehung für Sprachfunktionen, die möglicherweise nicht umgangen werden sollten. Auch warum dort aufhören? Traditionell hat sich DI darauf verlassen, dass jede Klasse eine Companion-Schnittstelle hat. Warum nicht auch alle diese Schnittstellen mit Anmerkungen entfernen?

Überlegen Sie sich die Alternative (dies wird C # sein, weil ich es besser kenne, aber es gibt wahrscheinlich ein genaues Äquivalent in Java):

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

Ich weiß schon einiges über diese Klasse. Es ist eine unveränderliche Klasse; state kann nur im Konstruktor gesetzt werden (Referenzen in diesem speziellen Fall). Und weil alles von einer Schnittstelle herrührt, kann ich Implementierungen im Konstruktor austauschen.

Jetzt muss mein DI-Container nur noch über den Konstruktor nachdenken, um zu bestimmen, welche Objekte neu erstellt werden müssen. Aber diese Reflexion wird auf erstklassige Weise über ein öffentliches Mitglied gemacht ; Das heißt, die Metadaten sind bereits Teil der Klasse und wurden im Konstruktor deklariert. Diese Methode hat den ausdrücklichen Zweck, der Klasse die Abhängigkeiten zur Verfügung zu stellen, die sie benötigt.

Zugegeben, das ist eine Menge Boilerplate, aber so wurde die Sprache entworfen. Anmerkungen scheinen ein schmutziger Hack für etwas zu sein, das in die Sprache selbst eingebaut werden sollte.


Wenn ich deinen Beitrag nicht falsch gelesen habe, siehst du das Beispiel nicht richtig an. Meine Implementierung von VeracodeServiceist fast identisch mit der, die Sie geschrieben haben (allerdings in Java vs C #). Das VeracodeServiceImplTestist eigentlich eine Unit Test Klasse. Die @InjectableFelder sind im Wesentlichen verspottete Objekte, die in den Kontext eingefügt werden. Das @TestedFeld ist das Objekt / die Klasse mit dem definierten DI-Konstruktor. Ich stimme Ihrer Ansicht zu, dass die Injektion von Konstruktoren der Injektion von Feldern vorgezogen wird. Wie ich bereits erwähnte, empfindet der JMockit-Autor das Gegenteil und ich versuche zu verstehen, warum
Eric B.

Wenn Sie sich den ersten Beitrag in dieser Diskussion ansehen , werden Sie sehen, dass ich in meiner Repo-Klasse fast die gleiche Definition habe.
Eric B.

Wenn Sie nur über Testklassen sprechen, spielt es möglicherweise keine Rolle. Weil Testklassen.
Robert Harvey

Nein - ich spreche nicht von Testklassen. Ich spreche von Produktionsklassen. Es kommt nur vor, dass das Beispiel in der Diskussionsgruppe ein Unit-Test ist, da das Framework ein Mocking / Testing-Framework ist. (Die ganze Diskussion dreht sich darum, ob das Verspottungs-Framework die Konstruktor-Injection unterstützen soll)
Eric B.

3
+1: Ja, keine Magie in der C # -Version. Es ist eine Standardklasse, sie hat Abhängigkeiten, sie werden alle an einem Punkt injiziert, bevor die Klasse verwendet wird. Die Klasse kann mit einem DI-Container verwendet werden oder nicht. Nichts in der Klasse sagt es IOC oder "Automatic Dependency Injection" Framework.
Binary Worrier

27

Das Argument von weniger Testinitialisierungsboiletplate ist gültig, aber es gibt andere Bedenken, die berücksichtigt werden müssen. Zunächst müssen Sie eine Frage beantworten:

Soll meine Klasse nur mit Reflektion instanziierbar sein?

Die Verwendung von Feldinjektionen bedeutet, die Kompatibilität einer Klasse mit Abhängigkeitsinjektionsumgebungen einzugrenzen, die Objekte mithilfe von Reflexion instanziieren und diese bestimmten Injektionsanmerkungen unterstützen. Einige Plattformen, die auf der Java-Sprache basieren, unterstützen nicht einmal Reflection ( GWT ), sodass feldinjizierte Klassen nicht mit ihnen kompatibel sind.

Das zweite Problem ist die Leistung . Ein Konstruktoraufruf (direkt oder durch Reflektion) ist immer schneller als eine Reihe von Reflektionsfeldzuweisungen. Frameworks für die Abhängigkeitsinjektion müssen eine Reflektionsanalyse verwenden, um einen Abhängigkeitsbaum zu erstellen und Reflektionskonstruktoren zu erstellen. Dieser Prozess verursacht zusätzliche Leistungseinbußen.

Die Leistung beeinflusst die Wartbarkeit . Wenn jede Testsuite in einem Abhängigkeitsinjektionsbehälter ausgeführt werden muss, kann ein Testlauf von einigen Tausend Einzeltests mehrere zehn Minuten dauern. Abhängig von der Größe einer Codebasis kann dies ein Problem sein.

Dies alles wirft viele neue Fragen auf:

  1. Wie hoch ist die Wahrscheinlichkeit, Teile des Codes auf einer anderen Plattform zu verwenden?
  2. Wie viele Unit-Tests werden geschrieben?
  3. Wie schnell muss ich jedes neue Release vorbereiten?
  4. ...

Im Allgemeinen sind diese Faktoren umso bedeutender, je größer und wichtiger ein Projekt ist. Auch in Bezug auf die Qualität möchten wir den Code im Allgemeinen in Bezug auf Kompatibilität, Testbarkeit und Wartbarkeit hoch halten. Aus philosophischer Sicht unterbricht die Feldinjektion die Kapselung , die eine der vier Grundlagen der objektorientierten Programmierung ist , die das Hauptparadigma von Java darstellt.

Viele, viele Argumente gegen die Feldinjektion.


3
+1 Du hast einen Nerv getroffen. Ich brauche keine Reflektion oder ein DI / IoC-Framework, um eine Klasse zu instanziieren. Es ist wie eine Fahrt nach Rom, um von Amsterdam nach Paris zu gelangen. Wohlgemerkt, ich liebe DI. Nur nicht sicher über die Rahmenbedingungen.
Marjan Venema

1
Gute Argumente. Es verbalisiert viele Gedanken, die ich selbst hatte, aber ich bin froh zu sehen, dass es anderen genauso geht. So erstaunlich ich auch die Feldinjektion fand, ich glaube, ich habe sie so geliebt, einfach weil sie "einfacher" war. Ich finde die Konstruktorinjektion jetzt viel klarer.
Eric B.

19

Die Feldinjektion erhält von mir ein definitives "Nein".

Wie Robert Harvey ist es für meinen Geschmack etwas zu automagisch. Ich bevorzuge expliziten Code gegenüber impliziten und toleriere Indirektion nur, wenn sie klare Vorteile bietet, da sie das Verständnis und die Argumentation von Code erschwert.

Wie Maciej Chałapuk brauche ich keine Reflektion oder ein DI / IoC-Framework, um eine Klasse zu instanziieren. Es ist wie eine Fahrt nach Rom, um von Amsterdam nach Paris zu gelangen.

Wohlgemerkt, ich liebe DI und all die Vorteile, die es bringt. Nur nicht sicher, ob die Frameworks zum Instanziieren einer Klasse benötigt werden. Vor allem nicht die Auswirkung, die IoC-Container oder andere DI-Frameworks auf den Testcode haben können.

Ich mag es, wenn meine Tests sehr einfach sind. Ich mag es nicht, die Indirektion des Einrichtens eines IoC-Containers oder eines anderen DI-Frameworks durchlaufen zu müssen, damit sie meine Klasse im Test einrichten. Es fühlt sich einfach unangenehm an.

Und das macht meine Tests vom globalen Status des Frameworks abhängig. Das heißt, ich kann nicht mehr zwei Tests parallel ausführen, für die eine Instanz der Klasse X mit unterschiedlichen Abhängigkeiten eingerichtet werden muss.

Zumindest bei der Konstruktor- und Setter-Injection haben Sie die Möglichkeit, die Frameworks und / oder Reflection in Ihren Tests nicht zu verwenden.


Wenn Sie zum Beispiel Mockito mockito.org verwenden , benötigen Sie kein DI / IoC-Framework, auch wenn Sie Feldinjektion verwenden, und können Tests parallel ausführen.
Hans-Peter Störr

1
@hstoerr Schön. Dies muss jedoch bedeuten, dass Mockito Reflection verwendet (oder wie auch immer das Java-Äquivalent dazu heißt) ...
Marjan Venema

5

Nun, das scheint eine polemische Diskussion zu sein. Das erste, was angesprochen werden muss, ist, dass sich die Feldinjektion von der Setter-Injektion unterscheidet . Ich arbeite bereits mit einigen Leuten zusammen, die glauben, dass Field and Setter Injection dasselbe ist.

Um es klar zu sagen:

Feldinjektion:

@Autowired
private SomeService someService;

Setterinjektion:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

Für mich haben sie jedoch die gleichen Bedenken. Und mein Favorit, die Konstruktorspritze:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

Ich habe die folgenden Gründe zu glauben, dass die Konstruktorinjektion besser ist als die Setter- / Feldinjektion (ich zitiere einige Spring-Links, um meinen Standpunkt zu demonstrieren):

  1. Kreisförmige Abhängigkeiten verhindern : Spring kann kreisförmige Abhängigkeiten zwischen den Beans erkennen, wie @Tomasz erklärt hat. Siehe auch das Frühlingsteam, das das sagt .
  2. Abhängigkeiten der Klasse liegen auf der Hand : Die Abhängigkeiten der Klasse im Konstruktor liegen auf der Hand , aber ausgeblendete mit Feld / Setter - Injektion. Ich fühle meinen Unterricht wie eine "Black Box" mit Feld / Setter-Injektion. Und es gibt (meiner Erfahrung nach) eine Tendenz, dass Entwickler sich nicht um die Klasse mit vielen Abhängigkeiten kümmern, was offensichtlich ist, wenn Sie versuchen, die Klasse mit Konstruktorinjektion zu verspotten (siehe nächster Punkt). Ein Problem, das die Entwickler stört, ist höchstwahrscheinlich gelöst. Außerdem verstößt eine Klasse mit zu vielen Abhängigkeiten möglicherweise gegen das Prinzip der Einzelverantwortung (Single Responsibility Principle, SRP).
  3. Einfach und zuverlässig zu verspotten : Im Vergleich zu Field Injection ist es einfacher, in einem Unit-Test zu verspotten, da Sie keine Kontext-Verspottung oder Reflektionstechnik benötigen, um die deklarierte private Bean innerhalb der Klasse zu erreichen, und Sie werden sich nicht davon täuschen lassen . Sie instanziieren einfach die Klasse und übergeben die verspotteten Bohnen. Einfach zu verstehen und leicht.
  4. Tools für die Codequalität können Ihnen dabei helfen : Tools wie Sonar können Sie warnen, dass die Klasse zu komplex wird, da eine Klasse mit vielen Parametern wahrscheinlich eine zu komplexe Klasse ist und einen Refaktor benötigt. Diese Tools können nicht dasselbe Problem identifizieren, wenn die Klasse die Field / Setter-Injektion verwendet.
  5. Besserer und unabhängiger Code : Durch die geringere @AutoWiredVerbreitung in Ihrem Code ist Ihr Code weniger vom Framework abhängig. Ich weiß, dass die Möglichkeit, das DI-Framework in einem Projekt einfach zu ändern, das nicht rechtfertigt (wer macht das, oder?). Aber dies zeigt für mich einen besseren Code (ich habe dies auf harte Weise mit EJB und Lookups gelernt ). Und mit Spring können Sie die @AutoWiredAnnotation sogar mit Konstruktorinjektion entfernen .
  6. Weitere Anmerkungen sind nicht immer die richtige Antwort : Aus bestimmten Gründen versuche ich wirklich, Anmerkungen nur dann zu verwenden, wenn sie wirklich nützlich sind.
  7. Sie können die Injektionen unveränderlich machen . Die Unveränderlichkeit ist ein sehr willkommenes Merkmal.

Wenn Sie nach weiteren Informationen suchen, empfehle ich diesen alten (aber immer noch relevanten) Artikel von Spring Blog, in dem wir erfahren, warum so viele Setter-Injections verwendet werden, und die Empfehlung, Konstruktor-Injections zu verwenden:

Die Setter-Injection wird viel häufiger verwendet als erwartet, da Frameworks wie Spring im Allgemeinen wesentlich besser für die Konfiguration durch Setter-Injection als durch Konstruktor-Injection geeignet sind

Und diese Anmerkung darüber, warum sie glauben, dass Konstruktor besser für Anwendungscode geeignet ist:

Ich denke, die Konstruktorinjektion ist für Anwendungscode viel nützlicher als für Framework-Code. Im Anwendungscode werden von Natur aus weniger optionale Werte benötigt, die Sie konfigurieren müssen

Abschließende Gedanken

Wenn Sie sich über den Vorteil des Konstruktors nicht wirklich sicher sind, ist möglicherweise die Idee, Setter (nicht Feld) mit Konstruktorinjektion zu mischen, eine Option, wie vom Spring-Team erläutert :

Da Sie sowohl Konstruktor- als auch Setter-basierte DI mischen können, ist es eine gute Faustregel, Konstruktorargumente für obligatorische Abhängigkeiten und Setter für optionale Abhängigkeiten zu verwenden

Aber denken Sie daran, dass die Feldinjektion wahrscheinlich diejenige ist , die unter den dreien vermieden werden sollte .


-5

Ist es wahrscheinlich, dass sich Ihre Abhängigkeit während des Lebens der Klasse ändert? Verwenden Sie in diesem Fall die Feld- / Eigenschaftsinjektion. Ansonsten Konstruktorinjektion verwenden.


2
Das erklärt eigentlich gar nichts. Warum private Felder einfügen, wenn Sie es stattdessen richtig machen können?
Robert Harvey

Wo steht etwas über private Felder? Ich spreche über die öffentliche Schnittstelle einer Klasse /
Darren Young

In dieser Frage gibt es kein Argument für Methodeninjektion oder Konstruktorinjektion. Der Autor von JMockit sieht beides als schlecht an. Schauen Sie sich den Titel der Frage an.
Robert Harvey

Sagt die Frage nicht Feldinjektion vs. Konstruktorinjektion?
Darren Young

1
Die Feldinjektion ist ein ganz anderes Tier. Sie stecken Werte in private Mitglieder.
Robert Harvey
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.