Muss die Abhängigkeitsinjektion auf Kosten der Kapselung gehen?

128

Wenn ich das richtig verstehe, besteht der typische Mechanismus für die Abhängigkeitsinjektion darin, entweder über einen Klassenkonstruktor oder über eine öffentliche Eigenschaft (Mitglied) der Klasse zu injizieren.

Dies legt die injizierte Abhängigkeit offen und verstößt gegen das OOP-Prinzip der Kapselung.

Bin ich richtig darin, diesen Kompromiss zu identifizieren? Wie gehen Sie mit diesem Problem um?

Bitte beachten Sie auch meine Antwort auf meine eigene Frage unten.

urig
quelle
7
Dies ist eine sehr kluge Frage imho
dfa
5
Die Beantwortung dieser Frage erfordert zunächst ein Argument darüber, was Kapselung bedeutet. ;)
Jeff Sternal
2
Die Kapselung wird durch die Schnittstellen aufrechterhalten. Sie legen die wesentlichen Merkmale des Objekts offen und verbergen Details wie Abhängigkeiten. Dies ermöglicht es uns, die Klassen bis zu einem gewissen Grad zu öffnen, um eine flexiblere Konfiguration bereitzustellen.
Lawrence Wagerfield

Antworten:

62

Es gibt eine andere Sichtweise auf dieses Problem, die Sie vielleicht interessant finden.

Wenn wir IoC / Abhängigkeitsinjektion verwenden, verwenden wir keine OOP-Konzepte. Zugegeben, wir verwenden eine OO-Sprache als 'Host', aber die Ideen hinter IoC stammen aus dem komponentenorientierten Software-Engineering, nicht aus OO.

Bei der Komponentensoftware dreht sich alles um das Verwalten von Abhängigkeiten. Ein häufig verwendetes Beispiel ist der Assembly-Mechanismus von .NET. Jede Assembly veröffentlicht die Liste der Assemblys, auf die sie verweist. Dies erleichtert das Zusammenführen (und Validieren) der für eine laufende Anwendung erforderlichen Teile erheblich.

Durch die Anwendung ähnlicher Techniken in unseren OO-Programmen über IoC möchten wir die Konfiguration und Wartung von Programmen vereinfachen. Das Veröffentlichen von Abhängigkeiten (als Konstruktorparameter oder was auch immer) ist ein wesentlicher Bestandteil davon. Die Kapselung trifft nicht wirklich zu, da es in der komponenten- / serviceorientierten Welt keinen 'Implementierungstyp' gibt, aus dem Details austreten können.

Leider trennen unsere Sprachen die feinkörnigen, objektorientierten Konzepte derzeit nicht von den gröberkörnigen komponentenorientierten Konzepten. Dies ist also eine Unterscheidung, die Sie nur im Kopf behalten müssen :)

Nicholas Blumhardt
quelle
18
Kapselung ist nicht nur eine ausgefallene Terminologie. Es ist eine echte Sache mit echten Vorteilen, und es spielt keine Rolle, ob Sie Ihr Programm als "komponentenorientiert" oder "objektorientiert" betrachten. Die Kapselung soll den Status Ihres Objekts / Ihrer Komponente / Ihres Dienstes / was auch immer vor unerwarteten Änderungen schützen, und IoC nimmt einen Teil dieses Schutzes weg, sodass es definitiv einen Kompromiss gibt.
Ron Inbar
1
Argumente, die über einen Konstruktor bereitgestellt werden, fallen immer noch in den Bereich der erwarteten Möglichkeiten, wie das Objekt "geändert" werden kann: Sie werden explizit verfügbar gemacht und Invarianten um sie herum werden erzwungen. Das Verstecken von Informationen ist der bessere Begriff für die Art von Datenschutz, auf die Sie sich beziehen, @RonInbar, und es ist nicht unbedingt immer von Vorteil (es erschwert das Entwirren der Nudeln ;-)).
Nicholas Blumhardt
2
Der springende Punkt bei OOP ist, dass das Nudelgewirr in separate Klassen unterteilt ist und dass Sie sich nur dann damit herumschlagen müssen, wenn es das Verhalten dieser bestimmten Klasse ist, die Sie ändern möchten (auf diese Weise verringert OOP die Komplexität). Eine Klasse (oder ein Modul) kapselt ihre Interna, während eine praktische öffentliche Schnittstelle verfügbar gemacht wird (auf diese Weise erleichtert OOP die Wiederverwendung). Eine Klasse, die ihre Abhängigkeiten über Schnittstellen verfügbar macht, schafft Komplexität für ihre Clients und ist daher weniger wiederverwendbar. Es ist auch von Natur aus zerbrechlicher.
Neutrino
1
Egal wie ich es betrachte, es scheint mir, dass DI einige der wertvollsten Vorteile von OOP ernsthaft untergräbt, und ich bin noch nicht auf eine Situation gestoßen, in der ich tatsächlich festgestellt habe, dass es auf eine Weise verwendet wird, die eine echte Lösung darstellt bestehendes Problem.
Neutrino
1
Hier ist eine andere Möglichkeit, das Problem zu erfassen: Stellen Sie sich vor, .NET-Assemblys haben "Kapselung" ausgewählt und nicht angegeben, auf welche anderen Assemblys sie sich verlassen. Es wäre eine verrückte Situation, Dokumente zu lesen und nur zu hoffen, dass nach dem Laden etwas funktioniert. Durch das Deklarieren von Abhängigkeiten auf dieser Ebene können automatisierte Tools die umfangreiche Zusammensetzung der App verarbeiten. Sie müssen schielen, um die Analogie zu sehen, aber ähnliche Kräfte wirken auf Komponentenebene. Es gibt Kompromisse und YMMV wie immer :-)
Nicholas Blumhardt
29

Es ist eine gute Frage - aber an einem gewissen Punkt, Verkapselung in seinen reinsten Form Bedürfnissen verletzt werden , wenn das Objekt jemals seine Abhängigkeit erfüllt haben. Einige Anbieter der Abhängigkeit müssen wissen, dass für das betreffende Objekt ein erforderlich ist Foo, und der Anbieter muss eine Möglichkeit haben Foo, das Objekt bereitzustellen.

Klassischerweise wird dieser letztere Fall wie gesagt durch Konstruktorargumente oder Setter-Methoden behandelt. Dies ist jedoch nicht unbedingt der Fall. Ich weiß, dass Sie mit den neuesten Versionen des Spring DI-Frameworks in Java beispielsweise private Felder (z. B. mit @Autowired) mit Anmerkungen versehen können und die Abhängigkeit durch Reflektion festgelegt wird, ohne dass Sie die Abhängigkeit offenlegen müssen eine der Klassen öffentliche Methoden / Konstruktoren. Dies könnte die Art von Lösung sein, nach der Sie gesucht haben.

Trotzdem denke ich auch nicht, dass die Konstruktorinjektion ein großes Problem darstellt. Ich hatte immer das Gefühl, dass Objekte nach der Erstellung vollständig gültig sein sollten, sodass alles, was sie zur Erfüllung ihrer Rolle benötigen (dh in einem gültigen Zustand sind), ohnehin über den Konstruktor bereitgestellt werden sollte. Wenn Sie ein Objekt haben, für dessen Arbeit ein Mitarbeiter erforderlich ist, scheint es mir in Ordnung zu sein, dass der Konstruktor diese Anforderung öffentlich ankündigt und sicherstellt, dass sie erfüllt ist, wenn eine neue Instanz der Klasse erstellt wird.

Idealerweise interagieren Sie beim Umgang mit Objekten ohnehin über eine Schnittstelle mit ihnen. Je mehr Sie dies tun (und Abhängigkeiten über DI verkabelt haben), desto weniger müssen Sie sich selbst mit Konstruktoren befassen. Im Idealfall behandelt Ihr Code keine konkreten Klasseninstanzen oder erstellt sie überhaupt nicht. Es wird also nur IFoodurch DI gegeben, ohne sich Gedanken darüber zu machen, was der Konstruktor von FooImplangibt, dass er seine Arbeit erledigen muss, und ohne sich dessen überhaupt bewusst zu FooImplsein. Unter diesem Gesichtspunkt ist die Einkapselung perfekt.

Dies ist natürlich eine Meinung, aber meiner Meinung nach verstößt DI nicht unbedingt gegen die Kapselung und kann tatsächlich helfen, indem es das gesamte notwendige Wissen über Interna an einem Ort zentralisiert. Dies ist nicht nur eine gute Sache für sich, sondern noch besser, dieser Ort befindet sich außerhalb Ihrer eigenen Codebasis, sodass keiner der von Ihnen geschriebenen Codes über die Abhängigkeiten der Klassen Bescheid wissen muss.

Andrzej Doyle
quelle
2
Gute Argumente. Ich rate davon ab, @Autowired für private Felder zu verwenden. das macht die Klasse schwer zu testen; Wie injiziert man dann Mocks oder Stubs?
Lumpynose
4
Ich bin nicht einverstanden. DI verletzt die Kapselung und dies kann vermieden werden. Zum Beispiel durch Verwendung eines ServiceLocator, der offensichtlich nichts über die Clientklasse wissen muss; Es muss nur über die Implementierungen der Foo-Abhängigkeit Bescheid wissen. In den meisten Fällen ist es jedoch am besten, einfach den "neuen" Operator zu verwenden.
Rogério
5
@Rogerio - Wahrscheinlich verhält sich jedes DI-Framework genau wie der von Ihnen beschriebene ServiceLocator. Der Client weiß nichts Spezifisches über Foo-Implementierungen, und das DI-Tool weiß nichts Spezifisches über den Client. Die Verwendung von "new" ist viel schlimmer, wenn die Kapselung verletzt wird, da Sie nicht nur die genaue Implementierungsklasse kennen müssen, sondern auch die genauen Klassen und Instanzen aller benötigten Abhängigkeiten.
Andrzej Doyle
4
Die Verwendung von "new" zum Instanziieren einer Hilfsklasse, die oft nicht einmal öffentlich ist, fördert die Kapselung. Die DI-Alternative wäre, die Hilfsklasse öffentlich zu machen und der Client-Klasse einen öffentlichen Konstruktor oder Setter hinzuzufügen. Beide Änderungen würden die Kapselung der ursprünglichen Hilfsklasse aufheben.
Rogério
1
"Es ist eine gute Frage - aber irgendwann muss die Kapselung in ihrer reinsten Form verletzt werden, wenn die Abhängigkeit des Objekts jemals erfüllt werden soll." Die Grundvoraussetzung Ihrer Antwort ist einfach falsch. Wie @ Rogério angibt, wird eine Abhängigkeit intern neu erstellt, und jede andere Methode, bei der ein Objekt seine eigenen Abhängigkeiten intern erfüllt, verletzt die Kapselung nicht.
Neutrino
17

Dies legt die injizierte Abhängigkeit offen und verstößt gegen das OOP-Prinzip der Kapselung.

Ehrlich gesagt verstößt alles gegen die Kapselung. :) Es ist eine Art Ausschreibungsprinzip, das gut behandelt werden muss.

Was verletzt also die Kapselung?

Vererbung tut .

"Da durch die Vererbung eine Unterklasse Details der Implementierung des übergeordneten Elements ausgesetzt wird, wird häufig gesagt, dass die Vererbung die Kapselung unterbricht." (Gang of Four 1995: 19)

Aspektorientierte Programmierung tut . Wenn Sie beispielsweise den Rückruf onMethodCall () registrieren, haben Sie die Möglichkeit, Code in die normale Methodenbewertung einzufügen und seltsame Nebenwirkungen usw. hinzuzufügen.

Freunddeklaration in C ++ tut .

Klassenerweiterung in Ruby tut . Definieren Sie eine Zeichenfolgenmethode einfach irgendwo neu, nachdem eine Zeichenfolgenklasse vollständig definiert wurde.

Nun, viele Dinge tun es .

Verkapselung ist ein gutes und wichtiges Prinzip. Aber nicht der einzige.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}
topright gamedev
quelle
3
"Das sind meine Prinzipien, und wenn du sie nicht magst ... nun, ich habe andere." (Groucho Marx)
Ron Inbar
2
Ich denke, das ist eine Art Punkt. Es ist eine abhängige Injektion gegenüber der Einkapselung. Verwenden Sie daher eine abhängige Injektion nur dort, wo sie signifikante Vorteile bringt. Es ist der DI überall, der DI einen schlechten
Richard Tingle
Nicht sicher , was diese Antwort ist versucht zu sagen , ... dass es in Ordnung ist Kapselung zu verletzen , wenn DI tut, dass es „immer in Ordnung“ , weil es ohnehin verletzt würde, oder auch nur , dass DI könnte ein Grund sein , Kapselung zu verletzen? Andererseits besteht heutzutage keine Notwendigkeit mehr, sich auf öffentliche Konstruktoren oder Eigenschaften zu verlassen, um Abhängigkeiten einzufügen. Stattdessen können wir in private mit Anmerkungen versehene Felder einfügen, was einfacher ist (weniger Code) und die Kapselung beibehält. So können wir beide Prinzipien gleichzeitig nutzen.
Rogério
Die Vererbung verletzt im Prinzip nicht die Kapselung, obwohl dies der Fall sein kann, wenn die übergeordnete Klasse schlecht geschrieben ist. Die anderen Punkte, die Sie ansprechen, sind ein ziemlich Randprogrammierparadigma und mehrere Sprachfunktionen, die wenig mit Architektur oder Design zu tun haben.
Neutrino
13

Ja, DI verletzt die Kapselung (auch als "Verstecken von Informationen" bezeichnet).

Das eigentliche Problem tritt jedoch auf, wenn Entwickler es als Ausrede verwenden, um gegen die Prinzipien KISS (kurz und einfach halten) und YAGNI (Sie werden es nicht brauchen) zu verstoßen.

Persönlich bevorzuge ich einfache und effektive Lösungen. Ich verwende meistens den "neuen" Operator, um zustandsbehaftete Abhängigkeiten zu instanziieren, wann und wo immer sie benötigt werden. Es ist einfach, gut gekapselt, leicht zu verstehen und leicht zu testen. Also warum nicht?

Rogério
quelle
Vorausdenken ist nicht schrecklich, aber ich stimme zu, halte es einfach, dumm, besonders wenn du es nicht brauchst! Ich habe gesehen, wie Entwickler Zyklen verschwendet haben, weil sie zu viel entworfen haben, um zu zukunftssicher zu sein, und basierend auf Vermutungen, nicht einmal Geschäftsanforderungen, die bekannt / vermutet werden.
Jacob McKay
5

Ein guter Injektionsinjektionsbehälter / -system ermöglicht eine Konstruktorinjektion. Die abhängigen Objekte werden gekapselt und müssen überhaupt nicht öffentlich zugänglich gemacht werden. Wenn Sie ein DP-System verwenden, "kennt" keiner Ihrer Codes die Details der Konstruktion des Objekts, möglicherweise sogar einschließlich des zu konstruierenden Objekts. In diesem Fall gibt es mehr Kapselung, da fast Ihr gesamter Code nicht nur vor dem Wissen über die gekapselten Objekte geschützt ist, sondern auch nicht an der Objektkonstruktion beteiligt ist.

Nun gehe ich davon aus, dass Sie mit dem Fall vergleichen, in dem das erstellte Objekt seine eigenen gekapselten Objekte erstellt, höchstwahrscheinlich in seinem Konstruktor. Mein Verständnis von DP ist, dass wir diese Verantwortung vom Objekt nehmen und es jemand anderem geben wollen. Zu diesem Zweck verfügt die "andere Person", in diesem Fall der DP-Container, über intimes Wissen, das die Kapselung "verletzt". Der Vorteil ist, dass es dieses Wissen selbst aus dem Objekt zieht. Jemand muss es haben. Der Rest Ihrer Anwendung tut dies nicht.

Ich würde es so sehen: Der Abhängigkeitsinjektionsbehälter / das System verletzt die Kapselung, Ihr Code jedoch nicht. Tatsächlich ist Ihr Code "gekapselt" wie nie zuvor.

Rechnung
quelle
3
Wenn Sie eine Situation haben, in der das Clientobjekt seine Abhängigkeiten direkt instanziieren kann, warum nicht? Es ist definitiv die einfachste Sache und verringert nicht unbedingt die Testbarkeit. Neben der Einfachheit und besseren Kapselung ist es auch einfacher, zustandsbehaftete Objekte anstelle zustandsloser Singletons zu haben.
Rogério
1
Zusätzlich zu dem, was @ Rogério gesagt hat, ist es möglicherweise auch wesentlich effizienter. Nicht jede Klasse, die jemals in der Geschichte der Welt geschaffen wurde, musste jede einzelne ihrer Abhängigkeiten für die gesamte Lebensdauer des Besitzobjekts instanziieren lassen. Ein Objekt, das DI verwendet, verliert die grundlegendste Kontrolle über seine eigenen Abhängigkeiten, nämlich ihre Lebensdauer.
Neutrino
5

Wie Jeff Sternal in einem Kommentar zu der Frage betonte, hängt die Antwort vollständig davon ab, wie Sie die Kapselung definieren .

Es scheint zwei Hauptlager zu geben, was Kapselung bedeutet:

  1. Alles, was mit dem Objekt zusammenhängt, ist eine Methode für ein Objekt. Also, ein Filekann bezwecken Methoden Save, Print, Display, ModifyTextetc.
  2. Ein Objekt ist seine eigene kleine Welt und hängt nicht vom Verhalten von außen ab.

Diese beiden Definitionen stehen in direktem Widerspruch zueinander. Wenn sich ein FileObjekt selbst drucken kann, hängt dies stark vom Verhalten des Druckers ab. Wenn es jedoch nur etwas weiß , das für es gedruckt werden kann (eine IFilePrinteroder eine solche Schnittstelle), muss das FileObjekt nichts über das Drucken wissen. Wenn Sie also damit arbeiten, werden weniger Abhängigkeiten in das Objekt eingebracht.

Wenn Sie die erste Definition verwenden, wird die Kapselung durch die Abhängigkeitsinjektion unterbrochen. Aber ehrlich gesagt weiß ich nicht, ob mir die erste Definition gefällt - sie skaliert eindeutig nicht (wenn ja, wäre MS Word eine große Klasse).

Auf der anderen Seite ist die Abhängigkeitsinjektion fast obligatorisch, wenn Sie die zweite Definition der Kapselung verwenden.

Kyoryu
quelle
Ich stimme Ihnen in Bezug auf die erste Definition definitiv zu. Es verstößt auch gegen SoC, was wohl eine der Hauptsünden der Programmierung ist und wahrscheinlich einer der Gründe, warum es nicht skaliert.
Marcus Stade
4

Es verletzt nicht die Kapselung. Sie stellen einen Mitarbeiter zur Verfügung, aber die Klasse kann entscheiden, wie er verwendet wird. Solange du Tell folgst, frag nicht, ob die Dinge in Ordnung sind. Ich finde die Konstruktorinjektion vorzuziehen, aber Setter können in Ordnung sein, solange sie klug sind. Das heißt, sie enthalten Logik, um die Invarianten beizubehalten, die die Klasse darstellt.

Jason Watkins
quelle
1
Weil es ... nicht? Wenn Sie einen Logger haben und eine Klasse, die den Logger benötigt, verstößt die Übergabe des Loggers an diese Klasse nicht gegen die Kapselung. Und das ist alles, was Abhängigkeitsinjektion ist.
Jrockway
3
Ich denke, Sie verstehen die Kapselung falsch. Nehmen Sie zum Beispiel eine naive Datumsklasse. Intern kann es Instanzvariablen für Tag, Monat und Jahr geben. Wenn diese als einfache Setter ohne Logik verfügbar gemacht würden, würde dies die Kapselung unterbrechen, da ich so etwas wie Monat auf 2 und Tag auf 31 setzen könnte. Wenn andererseits die Setter klug sind und die Invarianten überprüfen, sind die Dinge in Ordnung . Beachten Sie auch, dass ich in der letzteren Version den Speicher auf Tage seit dem 1.1.1970 ändern kann und nichts, was die Schnittstelle verwendet, dies berücksichtigen muss, vorausgesetzt, ich schreibe die Methoden Tag / Monat / Jahr entsprechend um.
Jason Watkins
2
DI verletzt definitiv die Kapselung / das Verstecken von Informationen. Wenn Sie eine private interne Abhängigkeit in etwas verwandeln, das in der öffentlichen Schnittstelle der Klasse verfügbar ist, haben Sie per Definition die Kapselung dieser Abhängigkeit unterbrochen.
Rogério
2
Ich habe ein konkretes Beispiel, bei dem ich denke, dass die Kapselung durch DI beeinträchtigt wird. Ich habe einen FooProvider, der "foo-Daten" von der Datenbank erhält, und einen FooManager, der sie zwischenspeichert und Daten über dem Anbieter berechnet. Ich hatte Konsumenten meines Codes, die fälschlicherweise nach Daten zum FooProvider gingen, wo ich sie lieber weggekapselt hätte, damit sie nur den FooManager kennen. Dies ist im Grunde der Auslöser für meine ursprüngliche Frage.
Urig
1
@ Rogerio: Ich würde argumentieren, dass der Konstruktor nicht Teil der öffentlichen Schnittstelle ist, da er nur im Kompositionsstamm verwendet wird. Somit wird die Abhängigkeit nur von der Kompositionswurzel "gesehen". Die einzige Verantwortung der Kompositionswurzel besteht darin, diese Abhängigkeiten miteinander zu verbinden. Die Verwendung der Konstruktorinjektion unterbricht also keine Einkapselung.
Jay Sullivan
4

Dies ähnelt der optimierten Antwort, aber ich möchte laut denken - vielleicht sehen andere die Dinge auch so.

  • Classical OO verwendet Konstruktoren, um den öffentlichen "Initialisierungs" -Vertrag für Verbraucher der Klasse zu definieren (Ausblenden ALLER Implementierungsdetails; auch bekannt als Kapselung). Dieser Vertrag kann sicherstellen, dass Sie nach der Instanziierung ein gebrauchsfertiges Objekt haben (dh keine zusätzlichen Initialisierungsschritte, an die sich der Benutzer erinnern muss (äh, vergessen)).

  • (Konstruktor) DI unterbricht unbestreitbar die Kapselung, indem es Implementierungsdetails über diese öffentliche Konstruktorschnittstelle blutet . Solange wir den öffentlichen Konstruktor weiterhin für die Definition des Initialisierungsvertrags für Benutzer verantwortlich machen, haben wir einen schrecklichen Verstoß gegen die Kapselung verursacht.

Theoretisches Beispiel:

Class Foo verfügt über 4 Methoden und benötigt eine Ganzzahl für die Initialisierung. Der Konstruktor sieht also wie Foo (int size) aus, und Benutzern der Klasse Foo ist sofort klar, dass sie bei der Instanziierung eine Größe angeben müssen, damit Foo funktioniert.

Angenommen , diese spezielle Implementierung von Foo benötigt möglicherweise auch ein IWidget , um ihre Aufgabe zu erfüllen . Durch die Konstruktorinjektion dieser Abhängigkeit würden wir einen Konstruktor wie Foo erstellen (int size, IWidget-Widget).

Was darüber ärgert mich jetzt haben wir einen Konstruktor, ist Blending Initialisierungsdaten mit Abhängigkeiten - ein Eingang von Interesse für den Benutzer der Klasse ( Größe ), die andere eine interne Abhängigkeit besteht darin , dass nur die Benutzer zu verwirren dient und ist eine Implementierung Detail ( Widget ).

Der Größenparameter ist KEINE Abhängigkeit - es ist einfach ein Initialisierungswert pro Instanz. IoC eignet sich hervorragend für externe Abhängigkeiten (wie Widgets), jedoch nicht für die Initialisierung des internen Status.

Schlimmer noch, was ist, wenn das Widget nur für 2 der 4 Methoden dieser Klasse erforderlich ist? Ich kann Instanziierungs-Overhead für Widget verursachen, obwohl es möglicherweise nicht verwendet wird!

Wie kann man dies kompromittieren / in Einklang bringen?

Ein Ansatz besteht darin, ausschließlich auf Schnittstellen umzuschalten, um den Betriebsvertrag zu definieren. und die Verwendung von Konstruktoren durch Benutzer abschaffen. Um konsistent zu sein, müsste auf alle Objekte nur über Schnittstellen zugegriffen und nur über eine Art Resolver (wie einen IOC / DI-Container) instanziiert werden. Nur der Container kann Dinge instanziieren.

Das kümmert sich um die Widget-Abhängigkeit, aber wie initialisieren wir "Größe", ohne auf eine separate Initialisierungsmethode auf der Foo-Oberfläche zurückzugreifen? Mit dieser Lösung haben wir die Möglichkeit verloren, sicherzustellen, dass eine Instanz von Foo zum Zeitpunkt des Abrufs der Instanz vollständig initialisiert ist. Schade, weil mir die Idee und Einfachheit der Konstruktorinjektion sehr gut gefällt .

Wie erreiche ich eine garantierte Initialisierung in dieser DI-Welt, wenn die Initialisierung MEHR als NUR externe Abhängigkeiten ist?

shawnT
quelle
Update: Ich habe gerade festgestellt, dass Unity 2.0 die Bereitstellung von Werten für Konstruktorparameter (z. B. Statusinitialisierer) unterstützt, während der normale Mechanismus für die IoC von Abhängigkeiten während der Auflösung () weiterhin verwendet wird. Vielleicht unterstützen auch andere Container dies? Das löst die technische Schwierigkeit, Zustand init und DI in einem Konstruktor zu mischen, verletzt aber immer noch die Einkapselung!
shawnT
Ich höre dich. Ich habe diese Frage gestellt, weil auch ich das Gefühl habe, dass zwei gute Dinge (DI & Kapselung) auf Kosten des anderen gehen. Übrigens, in Ihrem Beispiel, in dem nur 2 von 4 Methoden das IWidget benötigen, würde dies anzeigen, dass die anderen 2 meiner Meinung nach zu einer anderen Komponente gehören.
Urig
3

Reine Einkapselung ist ein Ideal, das niemals erreicht werden kann. Wenn alle Abhängigkeiten ausgeblendet wären, wäre DI überhaupt nicht erforderlich. Stellen Sie sich das so vor: Wenn Sie wirklich private Werte haben, die innerhalb des Objekts verinnerlicht werden können, beispielsweise den ganzzahligen Wert der Geschwindigkeit eines Autoobjekts, dann haben Sie keine externe Abhängigkeit und müssen diese Abhängigkeit nicht invertieren oder injizieren. Diese Art von internen Statuswerten, die nur von privaten Funktionen verarbeitet werden, möchten Sie immer kapseln.

Wenn Sie jedoch ein Auto bauen, das eine bestimmte Art von Motorobjekt möchte, besteht eine externe Abhängigkeit. Sie können diese Engine - zum Beispiel die neue GMOverHeadCamEngine () - entweder intern im Konstruktor des Autoobjekts instanziieren, die Kapselung beibehalten, aber eine viel heimtückischere Kopplung an eine konkrete Klasse GMOverHeadCamEngine herstellen, oder Sie können sie injizieren, damit Ihr Autoobjekt arbeiten kann Agnostisch (und viel robuster) zum Beispiel auf einer Schnittstellen-IEngine ohne die konkrete Abhängigkeit. Ob Sie einen IOC-Container oder einen einfachen DI verwenden, um dies zu erreichen, ist nicht der Punkt - der Punkt ist, dass Sie ein Auto haben, das viele Arten von Motoren verwenden kann, ohne an einen von ihnen gekoppelt zu sein, wodurch Ihre Codebasis flexibler und flexibler wird weniger anfällig für Nebenwirkungen.

DI ist keine Verletzung der Kapselung, sondern eine Möglichkeit, die Kopplung zu minimieren, wenn die Kapselung in praktisch jedem OOP-Projekt selbstverständlich unterbrochen wird. Durch das externe Injizieren einer Abhängigkeit in eine Schnittstelle werden die Nebenwirkungen der Kopplung minimiert, und Ihre Klassen können hinsichtlich der Implementierung agnostisch bleiben.

Dave Sims
quelle
3

Es hängt davon ab, ob die Abhängigkeit wirklich ein Implementierungsdetail ist oder etwas, über das der Client auf die eine oder andere Weise Bescheid wissen möchte / muss. Relevant ist, auf welche Abstraktionsebene die Klasse abzielt. Hier sind einige Beispiele:

Wenn Sie eine Methode haben, die das Caching unter der Haube verwendet, um Anrufe zu beschleunigen, sollte das Cache-Objekt ein Singleton oder etwas anderes sein und nicht injiziert werden. Die Tatsache, dass der Cache überhaupt verwendet wird, ist ein Implementierungsdetail, um das sich die Clients Ihrer Klasse nicht kümmern sollten.

Wenn Ihre Klasse Datenströme ausgeben muss, ist es wahrscheinlich sinnvoll, den Ausgabestream einzufügen, damit die Klasse die Ergebnisse problemlos in ein Array, eine Datei oder an einen anderen Ort ausgeben kann, an dem jemand anderes die Daten senden möchte.

Nehmen wir für eine Grauzone an, Sie haben eine Klasse, die eine Monte-Carlo-Simulation durchführt. Es braucht eine Quelle der Zufälligkeit. Einerseits ist die Tatsache, dass dies erforderlich ist, ein Implementierungsdetail, da es dem Kunden wirklich egal ist, woher die Zufälligkeit kommt. Andererseits kann eine Injektion sinnvoll sein, da reale Zufallszahlengeneratoren Kompromisse zwischen dem Grad der Zufälligkeit, der Geschwindigkeit usw. eingehen, den der Client möglicherweise steuern möchte, und der Client möglicherweise das Seeding steuern möchte, um ein wiederholbares Verhalten zu erzielen. In diesem Fall würde ich vorschlagen, eine Möglichkeit zum Erstellen der Klasse ohne Angabe eines Zufallszahlengenerators anzubieten und standardmäßig einen threadlokalen Singleton zu verwenden. Wenn eine feinere Steuerung erforderlich ist, stellen Sie einen anderen Konstruktor bereit, mit dem eine Zufallsquelle injiziert werden kann.

Dsimcha
quelle
2

Ich glaube an Einfachheit. Das Anwenden von IOC / Dependecy Injection in Domänenklassen führt zu keiner Verbesserung, außer dass die Verwaltung des Codes durch externe XML-Dateien, die die Beziehung beschreiben, erheblich erschwert wird. Viele Technologien wie EJB 1.0 / 2.0 und Struts 1.1 kehren sich um, indem sie das in XML eingegebene Material reduzieren und versuchen, es als Annoation usw. in Code einzufügen. Wenn Sie also IOC für alle von Ihnen entwickelten Klassen anwenden, ist der Code nicht sinnvoll.

IOC hat Vorteile, wenn das abhängige Objekt zur Kompilierungszeit nicht zur Erstellung bereit ist. Dies kann in den meisten Komponenten der abstrakten Architektur auf Infrasture-Ebene geschehen, wenn versucht wird, ein gemeinsames Basisframework zu erstellen, das möglicherweise für verschiedene Szenarien funktionieren muss. An diesen Orten ist die Verwendung von IOC sinnvoller. Dies macht den Code jedoch nicht einfacher / wartbarer.

Wie alle anderen Technologien hat auch dies Vor- und Nachteile. Ich mache mir Sorgen, dass wir die neuesten Technologien an allen Orten implementieren, unabhängig von ihrer besten Kontextnutzung.

Senthil Kumar Vaithilingam
quelle
2

Die Kapselung wird nur unterbrochen, wenn eine Klasse sowohl für die Erstellung des Objekts verantwortlich ist (für die Kenntnis der Implementierungsdetails erforderlich ist) als auch die Klasse verwendet (für die keine Kenntnis dieser Details erforderlich ist). Ich werde erklären warum, aber zuerst eine kurze Auto-Anaologie:

Als ich meinen alten Kombi von 1971 fuhr, konnte ich das Gaspedal drücken und es ging (etwas) schneller. Ich musste nicht wissen warum, aber die Leute, die den Kombi in der Fabrik bauten, wussten genau warum.

Aber zurück zur Codierung. Bei der Kapselung wird "ein Implementierungsdetail vor etwas verborgen, das diese Implementierung verwendet". Die Kapselung ist eine gute Sache, da sich die Implementierungsdetails ändern können, ohne dass der Benutzer der Klasse dies weiß.

Bei Verwendung der Abhängigkeitsinjektion wird die Konstruktorinjektion zum Erstellen von Service- Typ-Objekten verwendet (im Gegensatz zu Entitäts- / Wertobjekten, die den Status modellieren). Alle Mitgliedsvariablen im Diensttypobjekt stellen Implementierungsdetails dar, die nicht auslaufen sollten. zB Socket-Portnummer, Datenbankanmeldeinformationen, eine andere Klasse, die zum Durchführen der Verschlüsselung aufgerufen werden soll, ein Cache usw.

Der Konstruktor ist relevant, wenn die Klasse zum ersten Mal erstellt wird. Dies geschieht während der Bauphase, während Ihr DI-Container (oder Ihre Fabrik) alle Serviceobjekte miteinander verdrahtet. Der DI-Container kennt nur Implementierungsdetails. Es weiß alles über Implementierungsdetails, wie die Leute in der Kombi-Fabrik über Zündkerzen Bescheid wissen.

Zur Laufzeit wird das erstellte Serviceobjekt als apon bezeichnet, um echte Arbeit zu leisten. Zu diesem Zeitpunkt kennt der Aufrufer des Objekts nichts von den Implementierungsdetails.

Ich fahre mit meinem Kombi zum Strand.

Nun zurück zur Kapselung. Wenn sich die Implementierungsdetails ändern, muss sich die Klasse, die diese Implementierung zur Laufzeit verwendet, nicht ändern. Die Kapselung ist nicht gebrochen.

Ich kann mein neues Auto auch zum Strand fahren. Die Kapselung ist nicht gebrochen.

Wenn sich die Implementierungsdetails ändern, muss sich der DI-Container (oder die Factory) ändern. Sie haben nie versucht, Implementierungsdetails vor der Fabrik zu verbergen.

WW.
quelle
Wie würden Sie Ihre Fabrik testen? Das bedeutet, dass der Kunde über die Fabrik Bescheid wissen muss, um ein funktionierendes Auto zu erhalten. Dies bedeutet, dass Sie für jedes andere Objekt in Ihrem System eine Fabrik benötigen.
Rodrigo Ruiz
2

Nachdem ich mich ein wenig weiter mit dem Problem auseinandergesetzt habe, bin ich jetzt der Meinung, dass Dependency Injection (zu diesem Zeitpunkt) die Kapselung bis zu einem gewissen Grad verletzt. Verstehen Sie mich aber nicht falsch - ich denke, dass die Verwendung der Abhängigkeitsinjektion in den meisten Fällen den Kompromiss wert ist.

Der Grund, warum DI die Kapselung verletzt, wird klar, wenn die Komponente, an der Sie arbeiten, an eine "externe" Partei geliefert werden soll (denken Sie daran, eine Bibliothek für einen Kunden zu schreiben).

Wenn für meine Komponente Unterkomponenten über den Konstruktor (oder öffentliche Eigenschaften) eingefügt werden müssen, gibt es keine Garantie dafür

"Verhindern, dass Benutzer die internen Daten der Komponente in einen ungültigen oder inkonsistenten Zustand versetzen".

Gleichzeitig kann das nicht gesagt werden

"Benutzer der Komponente (andere Software) müssen nur wissen, was die Komponente tut, und können sich nicht von den Details ihrer Funktionsweise abhängig machen . "

Beide Zitate stammen aus Wikipedia .

Um ein konkretes Beispiel zu nennen: Ich muss eine clientseitige DLL bereitstellen, die die Kommunikation mit einem WCF-Dienst (im Wesentlichen einer Remote-Fassade) vereinfacht und verbirgt. Da es von 3 verschiedenen WCF-Proxy-Klassen abhängt, bin ich gezwungen, diese über den Konstruktor verfügbar zu machen, wenn ich den DI-Ansatz verwende. Damit lege ich die Interna meiner Kommunikationsschicht frei, die ich zu verbergen versuche.

Generell bin ich alles für DI. In diesem speziellen (extremen) Beispiel erscheint es mir gefährlich.

urig
quelle
2

DI verletzt die Kapselung für NICHT gemeinsam genutzte Objekte - Punkt. Freigegebene Objekte haben eine Lebensdauer außerhalb des zu erstellenden Objekts und müssen daher in das zu erstellende Objekt AGGREGIERT werden. Objekte, die für das zu erstellende Objekt privat sind, sollten in das erstellte Objekt zusammengesetzt werden. Wenn das erstellte Objekt zerstört wird, nimmt es das zusammengesetzte Objekt mit. Nehmen wir als Beispiel den menschlichen Körper. Was ist zusammengesetzt und was ist aggregiert. Wenn wir DI verwenden würden, hätte der Konstruktor des menschlichen Körpers Hunderte von Objekten. Viele der Organe sind beispielsweise (möglicherweise) austauschbar. Aber sie sind immer noch im Körper zusammengesetzt. Blutzellen werden jeden Tag im Körper gebildet (und zerstört), ohne dass äußere Einflüsse (außer Protein) erforderlich sind. So werden Blutzellen intern vom Körper erzeugt - neue Blutzelle ().

Befürworter von DI argumentieren, dass ein Objekt NIEMALS den neuen Operator verwenden sollte. Dieser "puristische" Ansatz verstößt nicht nur gegen die Kapselung, sondern auch gegen das Liskov-Substitutionsprinzip für jeden, der das Objekt erstellt.

Greg Brand
quelle
1

Ich hatte auch mit dieser Vorstellung zu kämpfen. Zunächst fühlte sich die "Anforderung", den DI-Container (wie Spring) zum Instanziieren eines Objekts zu verwenden, an, als würde man durch Reifen springen. Aber in Wirklichkeit ist es wirklich kein Reifen - es ist nur eine andere "veröffentlichte" Art, Objekte zu erstellen, die ich brauche. Sicher, die Kapselung ist "kaputt", weil jemand "außerhalb der Klasse" weiß, was sie braucht, aber es ist wirklich nicht der Rest des Systems, der das weiß - es ist der DI-Container. Nichts Magisches passiert anders, weil DI 'weiß', dass ein Objekt ein anderes braucht.

Tatsächlich wird es sogar noch besser - wenn ich mich auf Fabriken und Repositories konzentriere, muss ich nicht einmal wissen, dass DI überhaupt involviert ist! Das bringt für mich den Deckel wieder auf die Kapselung. Wütend!

n8wrl
quelle
1
Solange DI für die gesamte Instanziierungskette verantwortlich ist, stimme ich zu, dass eine Art Verkapselung auftritt. Art von, weil die Abhängigkeiten noch öffentlich sind und missbraucht werden können. Aber wenn irgendwo "oben" in der Kette jemand ein Objekt instanziieren muss, ohne dass er DI verwendet (vielleicht sind sie ein "Dritter"), wird es chaotisch. Sie sind Ihren Abhängigkeiten ausgesetzt und könnten versucht sein, sie zu missbrauchen. Oder sie möchten überhaupt nichts über sie wissen.
Urig
1

PS. Durch die Bereitstellung von Dependency Injection Sie nicht unbedingt brechen Encapsulation . Beispiel:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

Der Client-Code kennt die Implementierungsdetails noch nicht.

topright gamedev
quelle
Wie wird in Ihrem Beispiel etwas injiziert? (Mit Ausnahme der Benennung Ihrer Setter-Funktion)
foo
1

Vielleicht ist dies eine naive Art, darüber nachzudenken, aber was ist der Unterschied zwischen einem Konstruktor, der einen ganzzahligen Parameter aufnimmt, und einem Konstruktor, der einen Dienst als Parameter aufnimmt? Bedeutet dies, dass das Definieren einer Ganzzahl außerhalb des neuen Objekts und das Einspeisen in das Objekt die Kapselung unterbricht? Wenn der Dienst nur innerhalb des neuen Objekts verwendet wird, sehe ich nicht, wie dies die Kapselung unterbrechen würde.

Durch die Verwendung einer automatischen Verdrahtungsfunktion (z. B. Autofac für C #) wird der Code extrem sauber. Durch das Erstellen von Erweiterungsmethoden für den Autofac-Builder konnte ich eine Menge DI-Konfigurationscode ausschneiden, den ich im Laufe der Zeit hätte pflegen müssen, als die Liste der Abhängigkeiten wuchs.

blooware
quelle
Es geht nicht um Wert oder Service. Es geht um die Umkehrung der Kontrolle - ob der Konstruktor der Klasse die Klasse einrichtet oder ob dieser Dienst das Einrichten der Klasse übernimmt. Für die es Implementierungsdetails dieser Klasse kennen muss, damit Sie einen anderen Ort zum Verwalten und Synchronisieren haben.
foo
1

Ich denke, es ist selbstverständlich, dass DI zumindest die Einkapselung signifikant schwächt. Darüber hinaus sind hier einige andere Nachteile von DI zu berücksichtigen.

  1. Es macht die Wiederverwendung von Code schwieriger. Ein Modul, das ein Client verwenden kann, ohne explizit Abhängigkeiten angeben zu müssen, ist offensichtlich einfacher zu verwenden als eines, bei dem der Client die Abhängigkeiten dieser Komponente irgendwie herausfinden und sie dann irgendwie verfügbar machen muss. Beispielsweise kann für eine Komponente, die ursprünglich für die Verwendung in einer ASP-Anwendung erstellt wurde, erwartet werden, dass ihre Abhängigkeiten von einem DI-Container bereitgestellt werden, der Objektinstanzen eine Lebensdauer im Zusammenhang mit Client-http-Anforderungen bietet. Dies ist möglicherweise nicht einfach auf einem anderen Client zu reproduzieren, der nicht über denselben integrierten DI-Container wie die ursprüngliche ASP-Anwendung verfügt.

  2. Dies kann den Code anfälliger machen. Abhängigkeiten, die durch die Schnittstellenspezifikation bereitgestellt werden, können auf unerwartete Weise implementiert werden, was zu einer ganzen Klasse von Laufzeitfehlern führt, die mit einer statisch aufgelösten konkreten Abhängigkeit nicht möglich sind.

  3. Dies kann den Code weniger flexibel machen, da Sie möglicherweise weniger Auswahlmöglichkeiten haben, wie er funktionieren soll. Nicht jede Klasse muss alle ihre Abhängigkeiten für die gesamte Lebensdauer der besitzenden Instanz haben, aber bei vielen DI-Implementierungen haben Sie keine andere Option.

Vor diesem Hintergrund denke ich, dass die wichtigste Frage dann lautet: " Muss eine bestimmte Abhängigkeit überhaupt extern spezifiziert werden? ". In der Praxis habe ich es selten für notwendig gehalten, eine extern bereitgestellte Abhängigkeit zu erstellen, um das Testen zu unterstützen.

Wenn eine Abhängigkeit wirklich extern bereitgestellt werden muss, deutet dies normalerweise darauf hin, dass die Beziehung zwischen den Objekten eher eine Zusammenarbeit als eine interne Abhängigkeit ist. In diesem Fall ist das geeignete Ziel die Kapselung jeder Klasse und nicht die Kapselung einer Klasse innerhalb der anderen .

Nach meiner Erfahrung besteht das Hauptproblem bei der Verwendung von DI darin, dass unabhängig davon, ob Sie mit einem Anwendungsframework mit integriertem DI beginnen oder Ihrer Codebasis DI-Unterstützung hinzufügen, aus irgendeinem Grund davon ausgegangen wird, dass dies korrekt sein muss, da Sie DI-Unterstützung haben Weg, um alles zu instanziieren . Sie stellen sich einfach nie die Frage "Muss diese Abhängigkeit extern spezifiziert werden?". Schlimmer noch, sie versuchen auch, alle anderen zu zwingen, die DI-Unterstützung auch für alles zu nutzen.

Das Ergebnis ist, dass sich Ihre Codebasis unaufhaltsam in einen Zustand verwandelt, in dem das Erstellen einer Instanz von irgendetwas in Ihrer Codebasis Unmengen von stumpfen DI-Containerkonfigurationen erfordert und das Debuggen von Dingen doppelt so schwierig ist, da Sie die zusätzliche Arbeitslast haben, um herauszufinden, wie und wo etwas instanziiert wurde.

Meine Antwort auf die Frage lautet also: Verwenden Sie DI, um ein tatsächliches Problem zu identifizieren, das es für Sie löst und das Sie auf keine andere Weise einfacher lösen können.

Neutrino
quelle
0

Ich bin damit einverstanden, dass DI im Extremfall die Kapselung verletzen kann. Normalerweise legt DI Abhängigkeiten offen, die nie wirklich gekapselt wurden. Hier ist ein vereinfachtes Beispiel aus Miško Heverys Singletons: Pathologische Lügner :

Sie beginnen mit einem CreditCard-Test und schreiben einen einfachen Unit-Test.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

Nächsten Monat erhalten Sie eine Rechnung über 100 US-Dollar. Warum wurdest du angeklagt? Der Komponententest betraf eine Produktionsdatenbank. Intern ruft CreditCard an Database.getInstance(). Wenn Sie CreditCard so umgestalten, dass es einen DatabaseInterfaceKonstruktor enthält, wird die Tatsache sichtbar, dass eine Abhängigkeit besteht. Ich würde jedoch argumentieren, dass die Abhängigkeit von Anfang an nie gekapselt wurde, da die CreditCard-Klasse extern sichtbare Nebenwirkungen verursacht. Wenn Sie CreditCard ohne Refactoring testen möchten, können Sie die Abhängigkeit durchaus beobachten.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

Ich denke nicht, dass es sich lohnt, sich Sorgen zu machen, ob das Offenlegen der Datenbank als Abhängigkeit die Kapselung verringert, da es ein gutes Design ist. Nicht alle DI-Entscheidungen werden so einfach sein. Keine der anderen Antworten zeigt jedoch ein Gegenbeispiel.

Craig P. Motlin
quelle
1
Unit-Tests werden normalerweise vom Klassenautor geschrieben, daher ist es aus technischer Sicht in Ordnung, die Abhängigkeiten im Testfall zu formulieren. Wenn sich die Kreditkartenklasse später ändert, um eine Web-API wie PayPal zu verwenden, muss der Benutzer alles ändern, wenn es DIed ist. Unit-Tests werden normalerweise mit genauen Kenntnissen des zu testenden Themas durchgeführt (ist das nicht der springende Punkt?), Daher denke ich, dass Tests eher eine Ausnahme als ein typisches Beispiel sind.
Kizzx2
1
Der Zweck von DI besteht darin, die von Ihnen beschriebenen Änderungen zu vermeiden. Wenn Sie von einer Web-API zu PayPal wechseln, würden Sie die meisten Tests nicht ändern, da sie einen MockPaymentService verwenden und CreditCard mit einem PaymentService erstellt wird. Sie hätten nur eine Handvoll Tests, um die reale Interaktion zwischen CreditCard und einem echten PaymentService zu untersuchen, sodass zukünftige Änderungen sehr isoliert sind. Die Vorteile sind für Diagramme mit tieferen Abhängigkeiten noch größer (z. B. für eine Klasse, die von CreditCard abhängt).
Craig P. Motlin
@Craig p. Motlin Wie kann das CreditCardObjekt von einer WebAPI zu PayPal wechseln, ohne dass sich etwas außerhalb der Klasse ändern muss?
Ian Boyd
@Ian Ich erwähnte, dass CreditCard überarbeitet werden sollte, um ein DatabaseInterface in seinen Konstruktor aufzunehmen, das es vor Änderungen in den Implementierungen von DatabaseInterfaces schützt. Vielleicht muss das noch allgemeiner sein und ein StorageInterface verwenden, von dem WebAPI eine andere Implementierung sein könnte. PayPal ist jedoch auf dem falschen Niveau, da es eine Alternative zu CreditCard ist. Sowohl PayPal als auch CreditCard könnten PaymentInterface implementieren, um andere Teile der Anwendung von außerhalb dieses Beispiels abzuschirmen.
Craig P. Motlin
@ kizzx2 Mit anderen Worten, es ist Unsinn zu sagen, dass eine Kreditkarte PayPal verwenden sollte. Sie sind Alternativen im wirklichen Leben.
Craig P. Motlin
0

Ich denke, es ist eine Frage des Umfangs. Wenn Sie die Kapselung definieren (ohne zu wissen, wie), müssen Sie die gekapselte Funktionalität definieren.

  1. Klasse wie sie ist : Was Sie einkapseln, ist die einzige Verantwortung der Klasse. Was es zu tun weiß. Zum Beispiel Sortieren. Wenn Sie einen Komparator für die Bestellung einfügen, sagen wir, Kunden, ist das nicht Teil der gekapselten Sache: Quicksort.

  2. Konfigurierte Funktionalität : Wenn Sie eine sofort einsatzbereite Funktionalität bereitstellen möchten, stellen Sie keine QuickSort-Klasse bereit, sondern eine mit einem Comparator konfigurierte Instanz der QuickSort-Klasse. In diesem Fall muss der Code, der für das Erstellen und Konfigurieren verantwortlich ist, vor dem Benutzercode verborgen sein. Und das ist die Kapselung.

Wenn Sie Klassen programmieren, verwenden Sie Option 1, indem Sie einzelne Verantwortlichkeiten in Klassen implementieren.

Wenn Sie Anwendungen programmieren und etwas tun, das nützliche konkrete Arbeit leistet, verwenden Sie wiederholt Option 2.

Dies ist die Implementierung der konfigurierten Instanz:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

So verwendet es ein anderer Client-Code:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

Es ist gekapselt, da clientSorteres die Client-Verwendung nicht beeinträchtigt, wenn Sie die Implementierung ändern (Sie ändern die Bean-Definition). Wenn Sie XML-Dateien verwenden, bei denen alle zusammen geschrieben sind, sehen Sie möglicherweise alle Details. Aber glauben Sie mir, der Client-Code ( ClientService) weiß nichts über seinen Sortierer.

Helios
quelle
0

Es ist wahrscheinlich erwähnenswert, dass dies Encapsulationetwas perspektivabhängig ist.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

Aus der Perspektive von jemandem, der an der AKlasse arbeitet, Aweiß das zweite Beispiel viel weniger über die Natur vonthis.b

Während ohne DI

new A()

vs.

new A(new B())

Die Person, die diesen Code betrachtet, weiß mehr über die Art des Codes Aim zweiten Beispiel.

Mit DI befindet sich zumindest das gesamte durchgesickerte Wissen an einem Ort.

Tom B.
quelle