Frage

Wenn ich richtig verstehe, ist der typische Mechanismus für Dependency Injection entweder durch eine Klasse Konstruktor zu injizieren oder durch ein öffentliches Eigentum (Mitglied) der Klasse.

Dies legt die Abhängigkeit injiziert wird und verletzt das OOP Prinzip der Kapselung.

Bin ich richtig dieses Kompromisses bei der Identifizierung? Wie gehen Sie mit diesem Problem umgehen?

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

War es hilfreich?

Lösung

Es gibt eine andere Art und Weise zu diesem Thema suchen, die Sie interessant finden könnten.

Wenn wir IoC / Dependency Injection verwenden, sind wir mit OOP-Konzepte nicht. Zwar sind wir eine OO-Sprache als ‚Host‘ verwenden, aber die Ideen hinter IoC kommen aus bauteilorientierten Software-Engineering, nicht OO.

Component Software ist über die Abhängigkeiten verwalten - ein Beispiel im allgemeinen Gebrauch ist Assembly Mechanismus .NET. Jede Baugruppe veröffentlicht die Liste der Baugruppen, die es verweist, und das macht es viel einfacher, zusammen zu ziehen (und bestätigt), um die Stücke für eine laufende Anwendung benötigt wird.

Durch die Anwendung ähnliche Techniken in unseren OO-Programmen über IoC wollen wir Programme einfacher zu konfigurieren und warten zu machen. Publishing Abhängigkeiten (als Konstruktorparameter oder was auch immer) ist ein wichtiger Teil davon. Encapsulation nicht wirklich anwenden, wie sie in der Komponente / serviceorientierte Welt gibt es keinen ‚Implementierungstyp‘ für Details von auszulaufen.

Leider ist unsere Sprachen werden entmischen derzeit nicht die feinkörnig, objektorientierte Konzepte aus den grobkörnigen bauteilorientierte diejenigen, so ist dies eine Unterscheidung, die Sie in Ihrem Kopf zu halten, haben nur:)

Andere Tipps

Es ist eine gute Frage - aber an einem gewissen Punkt, Verkapselung in seiner reinsten Form muss verletzt werden, wenn das Objekt jemals seine Abhängigkeit erfüllt haben. Einige Anbieter der Abhängigkeit muss wissen beide, dass das Objekt in Frage erfordert eine Foo, und der Anbieter hat eine Art und Weise der Bereitstellung des Foo auf das Objekt haben.

Classically dieser letzteren Fall wird, wie Sie behandeln sagen, durch Konstruktorargumente oder Setter-Methoden. Dies ist jedoch nicht unbedingt wahr - ich weiß, dass die neuesten Versionen des Frühling DI Framework in Java, zum Beispiel, können Sie private Felder (zB mit @Autowired) mit Anmerkungen versehen und die Abhängigkeit wird durch Reflexion eingestellt werden, ohne dass Sie die Abhängigkeit belichten durch eine der Klassen öffentlichen Methoden / Konstrukteure. Dies könnte die Art von Lösung, die Sie suchen.

Wie gesagt, ich glaube nicht, dass Konstruktor Injektion großer Problem ist, auch nicht. Ich habe immer das Gefühl, dass Objekte nach dem Bau vollständig gültig sein sollte, so dass alles, was sie benötigen, um ihre Aufgabe zu erfüllen (das heißt in einem gültigen Zustand sein) sollte auf jeden Fall durch den Konstruktor geliefert werden. Wenn Sie ein Objekt haben, die einen Mitarbeiter erfordert zu arbeiten, wie es scheint gut zu mir, dass der Konstruktor öffentlich diese Anforderung bewirbt und stellt sicher, es ist erfüllt, wenn eine neue Instanz der Klasse erstellt wird.

Im Idealfall, wenn Umgang mit Objekten interagieren Sie mit ihnen über eine Schnittstelle wie auch immer, und je mehr Sie tun dies (und Abhängigkeiten durch DI verdrahtet), desto weniger haben tatsächlich mit Konstrukteuren, sich zu beschäftigen. Im Idealfall, Ihr Code nicht behandelt oder sogar überhaupt konkrete Instanzen von Klassen erstellen; so es wird nur eine IFoo durch DI gegeben, ohne sich Gedanken darüber, was der Konstruktor von FooImpl zeigt es seine Aufgabe tun muss, und in der Tat auch ohne sich dessen bewusst zu FooImpl der Existenz. Aus dieser Sicht ist die Verkapselung perfekt.

Dies ist eine Meinung, natürlich, aber meiner Meinung nach ist DI nicht unbedingt Verkapselung verletzen und in der Tat kann es helfen, indem sie alle notwendigen Kenntnisse über Interna an einem Ort zu zentralisieren. Dies ist nicht nur eine gute Sache an sich, aber noch besser ist dieser Ort außerhalb des eigenen Code-Basis, so dass keiner der Code, den Sie benötigt Schreib über Klassen Abhängigkeiten kennen.

  

Dies legt die Abhängigkeit injiziert wird und verletzt das OOP Prinzip der Kapselung.

Nun, ehrlich gesagt, gegen alles Verkapselung. :) Es ist eine Art eines Angebots Prinzip, das gut behandelt werden müssen.

Also, was gegen Kapselung?

Vererbung funktioniert .

  

„Weil Vererbung eine Unterklasse zu Einzelheiten ihrer Eltern Implementierung macht, ist es oft gesagt, dass‚Vererbung bricht Kapselung‘“. (Gang of Four 1995: 19)

Aspektorientierte Programmierung hat . Zum Beispiel, Sie onMethodCall () Callback registrieren und das gibt Ihnen eine große Chance, Code auf die normale Methode Auswertung zu injizieren, das Hinzufügen seltsame Nebenwirkungen etc.

Friend-Deklaration in C ++ funktioniert .

Klasse extention in Ruby funktioniert . neu definieren Sie einfach eine String-Methode irgendwo nach einer String-Klasse wurde vollständig definiert.

Nun, eine Menge Sachen funktioniert .

Die Einkapselung ist ein gutes und wichtiges Prinzip. Aber nicht der einzige.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

Ja, DI verletzt Kapselung (auch als "Information Hiding" bekannt).

Aber das eigentliche Problem kommt, wenn die Entwickler es als Vorwand verwenden, um die KISS zu verletzen (Keep It Short and Simple) und YAGNI (Yagni) Prinzipien.

Ich persönlich ziehe einfache und effektive Lösungen. Ich verwende meist den „neuen“ Operator Stateful Abhängigkeiten zu instanziiert wann und wo immer sie gebraucht werden. Es ist einfach, gut gekapselt, leicht zu verstehen und einfach zu testen. Also, warum nicht?

Ein gutes depenancy Injektions Container / System wird für Konstruktor Injektion ermöglichen. Die abhängigen Objekte werden gekapselt und müssen nicht öffentlich überhaupt ausgesetzt werden. Ferner kann durch ein DP-System, keine Ihres Code selbst „weiß“ die Details, wie das Objekt aufgebaut ist, möglicherweise sogar das Objekt einschließlich gebaut. Es gibt mehr Verkapselung in diesem Fall, da fast alle Ihren Code nicht nur aus der Kenntnis der eingekapselte Objekte abgeschirmt ist, nimmt jedoch nicht einmal in den Objekten Bau.

Nun, ich nehme an, dass Sie gegen den Fall vergleichen, wo das erstellte Objekt seine eigenen gekapselte Objekte erzeugt, höchstwahrscheinlich in seinem Konstruktor. Mein Verständnis von DP ist, dass wir von dem Objekt diese Verantwortung weg nehmen wollen und geben es an jemand anderen. Zu diesem Zweck des „jemand anders“, die der DP-Behälter in diesem Fall ist, tut intime Kenntnis hat, die „verletzt“ Verkapselung; Der Vorteil ist, dass es, dass das Wissen aus dem Objekt zieht, iteself. Jemand hat es zu haben. Der Rest der Anwendung nicht.

Ich würde denken, es auf diese Art und Weise: Der Dependency Injection Container / System verletzt Kapselung, aber Ihr Code nicht. In der Tat ist Ihr Code „verkapselt“ denn je.

Es verstößt nicht Kapselung. Sie bieten einen Mitarbeiter, aber die Klasse wird zu entscheiden, wie es verwendet wird. Solange Sie Sagen Sie nicht fragen Dinge sind in Ordnung. Ich finde constructer Injektion bevorzugt, aber Setter kann auch so lange in Ordnung, wie sie schlau sind. Das heißt, sie Logik enthalten, die Invarianten der Klasse repräsentiert aufrecht zu erhalten.

Dies ist ähnlich der upvoted Antwort, aber ich will laut denken -. Vielleicht auch andere sehen Dinge auf diese Weise auch

  • Klassische OO verwendet Konstrukteure die Öffentlichkeit „Initialisierung“ Vertrag für die Verbraucher der Klasse zu definieren (Ausblenden von allen Implementierungsdetails; aka Verkapselung). Dieser Vertrag kann dafür sorgen, dass nach der Instanzierung haben Sie ein ready-to-use-Objekt (das heißt keine zusätzliche Initialisierungsschritte (er vergisst) durch den Benutzer daran erinnert werden).

  • (Konstruktor) DI unleugbar bricht Verkapselung durch Ausbluten Implemenation Detail durch diese öffentliche Konstruktor-Schnittstelle. Solange wir noch den öffentlichen Konstruktor für die Definition der Initialisierung Vertrag für die Nutzer verantwortlich betrachten, haben wir eine schreckliche Verletzung der Einkapselung erstellt.

Theoretische Beispiel:

Klasse Foo hat 4 Methoden und braucht eine ganze Zahl für die Initialisierung, so Konstruktor sieht aus wie Foo (int size) und es ist sofort klar, Benutzer der Klasse foo , dass sie eine Größe bei Instanziierung um bereitstellen muss für foo zu arbeiten.

Sagen Sie diese spezielle Implementierung von Foo auch eine müssen IWidget , seine Arbeit zu tun. Constructor Injektion dieser Abhängigkeit würde uns einen Konstruktor haben wie Foo (int size, IWidget widget)

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

Der Parameter Größe ist nicht eine Abhängigkeit - es ist einfach eine pro-Instanz Initialisierungswert. IoC ist Dandy für externe Abhängigkeiten (wie Widgets), aber nicht für den internen Zustand der Initialisierung.

Noch schlimmer ist, was passiert, wenn das Widget nur für 2 der 4 Methoden dieser Klasse erforderlich ist; Ich kann Instanziierung Aufwand für Widget werden entstehen, auch wenn es nicht verwendet werden!

Wie Kompromiss / versöhnen das?

Ein Ansatz ist ausschließlich auf den Schnittstellen wechseln den Betrieb Vertrag zu definieren; und Abschaffung der Verwendung von Konstrukteuren von Nutzern. Um konsequent zu bleiben, müssten alle Objekte nur über Schnittstellen zugegriffen werden, und instanziiert nur durch irgendeine Form von Resolver (wie ein IOC / DI-Container). Nur wird der Behälter Dinge instanziieren.

Das kümmert sich um die Widget-Abhängigkeit, aber wie initialisieren wir „Größe“, ohne auf die Foo-Schnittstelle an eine separate Initialisierungsmethode zurückgreifen? Mit dieser Lösung haben wir verloren die Fähigkeit, um sicherzustellen, dass eine Instanz von Foo vollständig durch die Zeit, die Instanz erhalten initialisiert wird. Schade, weil ich wirklich wie die Idee und Einfachheit von Konstruktor Injektion.

Wie kann ich garantierte Initialisierung in dieser DI Welt erreichen, wenn die Initialisierung ist mehr als nur externe Abhängigkeiten?

Wie Jeff Sternal auf die Frage in einem Kommentar darauf hingewiesen, ist die Antwort ganz davon ab, wie Sie define Kapselung .

Es scheint zwei Hauptlager zu sein von dem, was Verkapselung bedeutet:

  1. Alles auf das Objekt bezogen ist eine Methode für ein Objekt. Also, ein File Objekt kann Methoden müssen Save, Print, Display, ModifyText, etc.
  2. Ein Objekt ist eine eigene kleine Welt, und hängt nicht von außen Verhalten.

Diese beiden Definitionen sind in direktem Widerspruch zueinander stehen. Wenn ein File Objekt selbst drucken kann, wird es hängt stark von dem Verhalten des Druckers. Auf der anderen Seite, wenn es nur weiß über etwas, das für sie (eine IFilePrinter oder eine solche Schnittstelle) drucken können, dann ist das File Objekt muss nicht alles über das Drucken wissen, und so die Arbeit mit es wird weniger Abhängigkeiten in das Objekt bringen.

Also, Dependency Injection bricht Verkapselung, wenn Sie die erste Definition verwenden. Aber, ehrlich gesagt weiß ich nicht, ob ich die erste Definition mag -. Es eindeutig nicht Maßstab (wenn es so wäre, MS Word würde eine große Klasse sein)

Auf der anderen Seite, Dependency Injection ist fast obligatorisch , wenn Sie die zweite Definition der Kapselung verwenden.

Reine Kapselung ist ein Ideal, das nie erreicht werden kann. Wenn alle Abhängigkeiten versteckt wurden dann würden Sie nicht die Notwendigkeit DI haben überhaupt. Denken Sie an es auf diese Weise, wenn Sie wirklich privat Werte haben, die in dem Objekt internalisiert werden können, sagen zum Beispiel der ganzzahlige Wert der Geschwindigkeit eines Autos Objekt, dann haben Sie keine externe Abhängigkeit und keine Notwendigkeit zu invertieren oder diese Abhängigkeit zu injizieren. Diese Art von internen Zustandswerten, die auf rein private Funktionen bedient werden, was Sie immer einzukapseln wollen.

Aber wenn Sie ein Auto Gebäude sind, die eine bestimmte Art von Motor Objekt will, dann haben Sie eine externe Abhängigkeit. Sie können entweder den Motor instanziiert - zum Beispiel neue GMOverHeadCamEngine () - intern im Konstruktor des Auto-Objekt, Verkapselung Erhaltung aber eine viel heimtückischer Kopplung an eine konkrete Klasse GMOverHeadCamEngine erstellen, oder Sie können es injizieren, so dass Ihr Car-Objekt zu betreiben agnostisch (und viel robuste) auf beispielsweise eine Schnittstelle iEngine ohne die konkrete Abhängigkeit. Ob Sie ein IOC-Container oder einfach DI verwenden, dies zu erreichen ist nicht der Punkt - der Punkt ist, dass Sie ein Auto haben, die ohne die mit einem von ihnen viele Arten von Motoren verwenden, wodurch Ihre Code-Basis flexibler und weniger anfällig für Nebenwirkungen.

DI ist nicht eine Verletzung der Verkapselung, es ist ein Weg, um die Kupplung zu minimieren, wenn Verkapselung notwendigerweise als selbstverständlich in jedem bekannten OOP Projekt gebrochen. Injizieren einer Abhängigkeit in eine Schnittstelle minimiert extern Kopplung Nebenwirkungen und ermöglicht Ihre Klassen über Implementierung Agnostiker zu bleiben.

ich in Einfachheit glauben. Die Anwendung IOC / Dependecy Injection in Domain-Klassen machen keine Verbesserung mit Ausnahme macht den Code wesentlich schwieriger zu Haupt durch eine externe XML-Dateien mit der Beschreibung der Beziehung. Viele Technologien wie EJB 1.0 / 2.0 und 1.1 Streben sind Umkehren durch die Reduzierung der Stoff, der Put in XML zurück und versuchen, sie als annoation in Code setzen usw. IOC So Anwendung für alle Klassen, die Sie entwickeln den Code nicht sinnvoll.

IOC hat es Vorteile, wenn das abhängige Objekt für die Erstellung bei der Kompilierung nicht bereit ist. Dies kann in den meisten der infrasture abstrakten Ebene Architekturkomponenten passiert, eine gemeinsame Basis Rahmen versucht zu etablieren, die für verschiedene Szenarien müssen arbeiten. In diesen Orten Nutzung macht IOC mehr Sinn. Dennoch ist dies nicht der Code einfacher / wartbar machen.

Wie alle anderen Technologien, auch dies hat Vor-und Nachteile. Meine Sorge ist, dass wir neueste Technologien in allen Orten, unabhängig von ihrer besten Kontextverwendung implementieren.

Es hängt davon ab, ob die Abhängigkeit ist wirklich eine Implementierung Detail oder etwas, das der Kunde möchte / müssen über in einer oder anderen Weise kennen zu lernen. Eine Sache, die relevant ist, welche Ebene der Abstraktion der Klasse zielt. Hier sind einige Beispiele:

Wenn Sie eine Methode, die Caching unter der Haube verwendet Anrufe zu beschleunigen, sollte der Cache-Objekt ein Singleton oder etwas sein und sollte nicht injiziert werden. Die Tatsache, dass der Cache überhaupt verwendet wird, ist eine Implementierung Detail, dass die Kunden Ihrer Klasse sollten etwa nicht kümmern müssen.

Wenn Ihre Klasse zu Ausgangsdatenströmen benötigt, macht es wahrscheinlich sinnvoll, den Ausgangsstrom zu injizieren, so dass die Klasse kann die Ergebnisse auf ein Array leicht Ausgabe, eine Datei, oder wo auch immer jemand anderes möchte die Daten senden.

Für eine Grauzone, sagen wir, Sie eine Klasse, die eine Monte-Carlo-Simulation der Fall ist. Es braucht eine Quelle der Zufälligkeit. Zum einen die Tatsache, dass es das braucht, ist eine in Implementierung Detail, dass der Kunde genau, dass es nicht egal, wo die Zufälligkeit herkommt. Auf der anderen Seite, da der realen Welt Zufallszahlengeneratoren Abwägungen zwischen dem Grad der Zufälligkeit machen, Geschwindigkeit usw., die der Client steuern wollen, und der Kunde möchte kann Seeding steuern wiederholbare Verhalten zu bekommen, kann Injektion Sinn machen. In diesem Fall würde ich vorschlagen, eine Möglichkeit zu schaffen, die Klasse anbieten, ohne einen Zufallszahlengenerator spezifiziert, und einen Thread-local Singleton als Standard verwenden. Wenn / wenn die Notwendigkeit für eine feinere Kontrolle entsteht, bietet eine weitere Konstruktor, der eine Zufallsquelle ermöglicht injiziert werden.

Die Einkapselung wird nur unterbrochen, wenn eine Klasse sowohl die Verantwortung, das Objekt zu erstellen hat (die Kenntnis der Details der Implementierung erfordert) und verwendet dann die Klasse (das erfordert nicht die Kenntnis dieser Details). Ich werde erklären, warum, aber zuerst ein schnelles Auto anaology:

  

Als ich meinen alten 1971 Kombi fahren,   Ich konnte das Gaspedal drücken und es   ging (leicht) schneller. Ich habe nicht   müssen wissen, warum, aber die Jungs, die   mit dem Kombi in der Fabrik gebaut wußte   genau, warum.

Aber zurück zur Codierung. Encapsulation ist "eine Implementierung Detail von etwas zu verbergen, dass die Umsetzung mit." Die Verkapselung ist eine gute Sache, weil die Details der Implementierung ohne dass der Benutzer der Klasse ändern können, zu kennen.

Wenn dependency injection Verwendung Konstruktor Injektion wird verwendet zur Konstruktion Service type Objekte (im Gegensatz zur Einheit / Wert Objekte, des Modellzustandes). Jedes Mitglied Variablen in Serviceart Objekt darstellen Implementierungsdetails, die nicht auslaufen sollte. z.B. Socket-Portnummer, Datenbank-Anmeldeinformationen, eine andere Klasse rufen Verschlüsselung durchzuführen, einen Cache, etc.

Der Konstruktor ist relevant, wenn die Klasse zunächst erstellt wird. Dies geschieht während der Bauphase, während die DI-Container (oder Fabrik) Drähte zusammen alle Dienstobjekte. Die DI-Container kennen nur Implementierungsdetails. Er weiß alles über Implementierungsdetails wie die Jungs an der Kombi-Fabrik über Zündkerzen kennen.

Zur Laufzeit, das Service-Objekt, das erstellt wurde, wird apon genannt einige echte Arbeit zu tun. Zu diesem Zeitpunkt weiß der Anrufer des Objekts nichts von den Implementierungsdetails.

  

Das ist ich meinen Kombi zum Strand fahren.

Nun, zurück zur Verkapselung. Wenn Implementierungsdetails zu ändern, dann die Klasse, die Implementierung zur Laufzeit verwenden, müssen nicht geändert werden. Die Verkapselung ist nicht gebrochen.

  

Ich kann mein neues Auto zum Strand fahren. Verkapselung ist nicht gebrochen.

Wenn Implementierungsdetails zu ändern, die DI-Container (oder Fabrik) ändern müssen. Sie haben nie versucht, Implementierungsdetails aus der Fabrik in erster Linie zu verstecken.

Nachdem mit der Frage gerungen ein wenig weiter, ich bin jetzt in der Meinung, dass Dependency Injection hat (zu diesem Zeitpunkt) verletze Verkapselung zu einem gewissen Grad. obwohl Sie mich nicht falsch -. Ich denke, dass Dependency Injection mit lohnt sich der Kompromiss in den meisten Fällen

Der Fall warum DI verletzt Verkapselung wird deutlich, wenn die Komponente auf dem Sie arbeiten ist zu einem „externen“ Partei geliefert werden (man denke an eine Bibliothek für einen Kunden zu schreiben).

Wenn meine Komponente Subkomponenten erfordert über den Konstruktor (oder öffentliche Eigenschaften) gibt es keine Garantie für

injiziert werden
  

„Verhindern Benutzer von den internen Daten des Bauteils in einen ungültigen oder inkonsistenten Zustand der Einstellung“.

Zur gleichen Zeit kann man nicht sagen, dass

  

„Anwender der Komponente (andere Teile der Software) nur müssen wissen, was die Komponente tut, und kann sich nicht auf die Details abhängig machen, wie sie es tut“ .

Beide Zitate sind von wikipedia .

ein spezifisches Beispiel zu geben: Ich brauche eine clientseitige DLL zu liefern, die Kommunikation zu einem WCF-Dienst vereinfacht und versteckt (im Wesentlichen eine Remote-Fassade). Weil es auf 3 verschiedenen WCF-Proxy-Klassen abhängig ist, wenn ich die DI Ansatz bin ich gezwungen, sie über den Konstruktor zu belichten. Damit entlarve ich die Interna meiner Kommunikationsschicht, die ich zu verbergen versuchen.

Im Allgemeinen ich bin für DI. In diesem speziellen (extremen) Beispiel kommt es mich so gefährlich.

ich mit diesem Begriff zu kämpfen als auch. Zunächst wird die Anforderung ', um den DI-Container zu verwenden (wie Spring) ein Objekt zu instanziiert fühlte sich wie durch Reifen springen. Aber in Wirklichkeit ist es wirklich kein hoop - es ist nur ein weiterer ‚veröffentlicht‘ Weg, um Objekte zu erstellen ich brauche. Sicher, ist Kapselung ‚gebrochen‘ becuase jemand ‚außerhalb der Klasse‘ weiß, was er braucht, aber es ist wirklich nicht der Rest des Systems, der weiß, dass - es ist die DI-Container. Nichts Magisches geschieht anders, weil DI ‚weiß‘ ein Objekt ein anderes braucht.

In der Tat es kommt noch besser - durch die Konzentration auf Fabriken und Repositorys Ich muss nicht einmal wissen, DI überhaupt beteiligt ist! Das ist für mich setzt den Deckel wieder auf Verkapselung. Puh!

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

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

Client-Code nicht kennt Implementierungsdetails noch.

Vielleicht ist dies eine naive Art und Weise darüber zu denken, aber was ist der Unterschied zwischen einem Konstruktor, der in einem Integer-Parametern und einen Konstruktor, der in einem Dienst als Parameter übernimmt? Bedeutet dies, dass eine ganze Zahl außerhalb des neuen Objektes definieren und in das Objekt bricht Verkapselung Fütterung? Wenn der Dienst nur innerhalb des neuen Objekts verwendet wird, sehe ich nicht, wie das wäre Verkapselung zu brechen.

Auch durch irgendeine Art von autowiring-Funktion (Autofac für C #, zum Beispiel), macht es den Code extrem sauber. Durch den Aufbau von Erweiterungsmethoden für die Autofac Builder, konnte ich eine Menge von DI Konfigurationscode auszuschneiden, die ich im Laufe der Zeit hatte zu halten, wie die Liste der Abhängigkeiten wuchs.

Ich denke, es selbstverständlich ist, dass erheblich schwächt DI Verkapselung zumindest. Zusätzlich zu, dass hier sind einige andere Nachteile von DI zu berücksichtigen.

  1. Es macht Code schwerer wiederzuverwenden. Ein Modul, das ein Client, ohne explizit verwenden kann, um Abhängigkeiten zu schaffen, ist offensichtlich einfacher ein zu verwenden, als in denen der Client entdecken, was Abhängigkeiten von dieser Komponente irgendwie hat und dann irgendwie sie zur Verfügung stellen. Zum Beispiel kann eine Komponente ursprünglich erstellt in einer ASP-Anwendung verwendet werden, kann die Abhängigkeiten erwarten von einem DI-Containern bereitgestellt haben, die Objektinstanzen mit Lebensdauern im Zusammenhang mit Client-HTTP-Anfragen zur Verfügung stellt. Dies kann nicht einfach sein, in einem anderen Client zu reproduzieren, die nicht mit den gleichen in DI-Containern wie die ursprüngliche ASP-Anwendung gebaut kommen.

  2. Es kann Code zerbrechlicher machen. Abhängigkeiten von Schnittstellen-Spezifikation bereitgestellt wird, können auf unerwartete Weise umgesetzt werden, den Anlass zu einer ganzen Klasse von Runtime-Fehlern gibt, die nicht möglich sind, mit einer statisch aufgelöst konkreten Abhängigkeit.

  3. Es kann Code weniger flexibel in dem Sinne, die Sie mit weniger Auswahl zu können am Ende, wie Sie es wollen arbeiten. Nicht jede Klasse muss alle seine Abhängigkeiten existieren für die gesamte Lebensdauer der besitzenden Instanz haben, noch mit vielen DI-Implementierungen Sie haben keine andere Wahl.

, die mit im Auge Ich denke, die wichtigste Frage ist dann, „ hat eine bestimmte Abhängigkeit muß extern überhaupt angegeben werden? “. In der Praxis fand ich es nur selten notwendig eine Abhängigkeit von außen nur zu unterstützen, den Test unterzogen zu machen.

Wenn eine Abhängigkeit muss wirklich von außen zugeführt werden, das legt nahe, normalerweise, dass die Beziehung zwischen den Objekten ist eine Zusammenarbeit eher als eine innere Abhängigkeit, wobei in diesem Fall das entsprechende Ziel ist dann die Einkapselung von jedem Klasse , anstatt Kapselung einer Klasse innerhalb des anderen.

Nach meiner Erfahrung ist das Hauptproblem die Verwendung von DI bezüglich ist, ob Sie mit einem Anwendungs-Framework beginnen mit in DI gebaut, oder Sie DI Unterstützung Ihrer Code-Basis hinzufügen, aus irgendeinem Grund Menschen gehen davon aus, dass Sie da DI Unterstützung haben das muss sein die richtige Art und Weise zu instanziieren alles . Sie sind einfach nicht einmal die Mühe, die Frage zu stellen „muss diese Abhängigkeit von außen vorgegeben werden?“. Und schlimmer noch, sie beginnen auch versuchen, jeden zu zwingen sonst die DI-Unterstützung zu verwenden, um alles zu.

Das Ergebnis davon ist, dass unerbittlich Ihre Code-Basis in einen Zustand zu übertragen beginnt, wo jede Instanz von etwas in Ihrer Code-Basis zu schaffen erfordert Unmengen von stumpfer DI-Containern Konfiguration, und alles, was das Debuggen ist doppelt so hart, weil Sie die zusätzliche Arbeitsbelastung zu versuchen, haben zu ermitteln, wie und wo alles instanziiert wurde.

So ist meine Antwort auf die Frage ist. Verwenden DI, wo Sie ein tatsächliches Problem identifizieren können, dass es für Dich löst, die Sie nicht mehr einfach eine andere Art und Weise lösen.

DI verletzt Encapsulation für nicht gemeinsam genutzte Objekte - Zeit. Gemeinsame Objekte haben eine Lebensdauer außerhalb des Objekts erstellt werden und somit aggregiert werden muss in das Objekt erstellt werden. Objekte, die auf das Objekt privat erstellt werden, sollte in dem erstellte Objekt zusammengesetzt sein - wenn das erstellte Objekt zerstört wird, nimmt es das zusammengesetzte Objekt mit ihm. Lassen Sie uns den menschlichen Körper als Beispiel. Was ist zusammengesetzt und was aggregiert. Wenn wir DI verwenden, würde der menschliche Körper Konstruktor 100 von Objekten haben. Viele der Organe, zum Beispiel sind (potentiell) austauschbar. Aber sind sie nach wie vor in den Körper zusammengesetzt ist. Die Blutzellen werden im Körper (und zerstört) jeden Tag, ohne dass äußere Einflüsse (außer Protein) erstellt. So werden Blutzellen intern durch den Körper geschaffen -. Neues bloodcell ()

Befürworter von DI argumentieren, dass ein Objekt NIEMALS den neuen Betreiber verwenden sollte. Der „Purist“ -Ansatz verletzt nicht nur Verkapselung, sondern auch das Liskov Substitutionsprinzip für wen schafft das Objekt aus.

Ich bin damit einverstanden, dass eine extreme eingenommen wird, kann DI Verkapselung verletzen. Normalerweise DI macht Abhängigkeiten, die nie wirklich eingekapselt wurden. Hier ist ein vereinfachtes Beispiel entlehnt Miško Hevery Singletons sind Pathologische Liars

Sie mit einem Test starten und Credit einen einfachen Unit-Test schreiben.

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

Im nächsten Monat erhalten Sie eine Rechnung für $ 100. Warum haben Sie geladen werden? Der Unit-Test beeinflusste eine Produktionsdatenbank. Intern ruft Credit Database.getInstance(). Refactoring Creditcard, so dass es ein DatabaseInterface in seinem Konstruktor nimmt die Tatsache macht, dass es die Abhängigkeit ist. Aber ich würde behaupten, dass die Abhängigkeit wurde nie beginnen eingekapselt, da die Klasse Credit verursacht äußerlich sichtbare Nebenwirkungen. Wenn Sie ohne Credit Refactoring testen möchten, können Sie sicher die Abhängigkeit beobachten.

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

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

Ich glaube nicht, es lohnt sich Sorgen, ob die Datenbank aussetzt als eine Abhängigkeit Verkapselung reduziert, weil es ein gutes Design ist. Nicht alle DI Entscheidungen werden so einfach sein. Doch keiner der anderen Antworten zeigt ein Gegenbeispiel.

Ich denke, es ist eine Frage des Umfangs. Wenn Sie Verkapselung definieren (nicht wissen zu lassen, wie) Sie müssen definieren, was die gekapselten Funktionalität ist.

  1. Klasse als ist: was Sie Einkapseln der einzige Verantwortung der Klasse ist. Was er weiß, wie zu tun ist. Durch beispielsweise das Sortieren. Wenn Sie einige Komparator für die Bestellung injizieren, sagen wir mal, Kunden, das ist nicht Teil des gekapselten Sache. Quicksort

  2. projektierter Funktionalität : Wenn Sie eine ready-to-use-Funktionalität zur Verfügung stellen möchten, dann sind Sie nicht QuickSort Klasse bereitstellt, sondern eine Instanz QuickSort Klasse mit einem Komparator konfiguriert. In diesem Fall wird der Code für die Erstellung und Konfiguration muss vom Anwendercode ausgeblendet werden. Und das ist die Verkapselung.

Wenn Sie Klassen programmieren, ist es, einzelne Aufgaben in Klassen Implementierung Sie verwenden Option 1.

Wenn Sie Anwendungen programmieren, ist es, etwas zu machen, das verpflichtet sich einige nützliche Beton Arbeit dann verwenden Sie repeteadily Option 2.

Dies ist die Implementierung der konfigurierten Instanz:

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

Dies ist, wie einige andere Client-Code verwenden es:

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

Es ist gekapselt, denn wenn Sie Implementierung ändern (Sie clientSorter Bean Definition ändern) ist es nicht Client-Anwendung nicht bricht. Vielleicht, wie Sie XML-Dateien mit allen geschrieben zusammen verwenden Sie alle Details sehen. Aber glauben Sie mir, der Client-Code (ClientService) nicht wissen nichts über seine Sortierer.

Es ist wahrscheinlich erwähnenswert, dass Encapsulation zu erwähnen ist etwas Perspektive abhängig.

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 Sicht von jemandem auf der A Klasse arbeitet, im zweiten Beispiel A weiß viel weniger über die Natur des this.b

Während ohne DI

new A()

vs

new A(new B())

Die Person an diesem Code sucht weiß mehr über die Natur der A im zweiten Beispiel.

Mit DI, zumindest alles, was durchgesickert Wissen an einem Ort ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top