IOC-Container brechen die OOP-Prinzipien


51

Was ist der Zweck von IOC-Containern? Die kombinierten Gründe dafür können auf das Folgende vereinfacht werden:

Bei Verwendung der OOP / SOLID-Entwicklungsprinzipien wird die Abhängigkeitsinjektion unordentlich. Entweder haben Sie die obersten Einstiegspunkte, die Abhängigkeiten für mehrere Ebenen unter sich verwalten und Abhängigkeiten rekursiv durch die Konstruktion leiten, oder Sie haben etwas doppelten Code in Factory- / Builder-Mustern und -Schnittstellen, mit denen Abhängigkeiten nach Bedarf erstellt werden.

Es gibt keine OOP / SOLID-Methode, um dies durchzuführen, UND es gibt einen super hübschen Code.

Wenn diese vorherige Aussage wahr ist, wie machen es IOC-Container? Soweit ich weiß, wenden sie keine unbekannte Technik an, die mit manueller DI nicht möglich ist. Daher besteht die einzige Erklärung darin, dass IOC-Container die OOP / SOLID-Prinzipien durch die Verwendung von privaten Zugriffsmethoden für statische Objekte brechen.

Brechen IOC-Container die folgenden Prinzipien hinter den Kulissen? Dies ist die eigentliche Frage, da ich ein gutes Verständnis habe, aber das Gefühl habe, dass jemand anderes ein besseres Verständnis hat:

  1. Umfangskontrolle . Der Umfang ist der Grund für fast jede Entscheidung, die ich für mein Code-Design treffe. Block, Lokal, Modul, Statisch / Global. Der Geltungsbereich sollte sehr eindeutig sein, so viel wie möglich auf Blockebene und so wenig wie möglich auf statischer Ebene. Sie sollten Deklarationen, Instantiierungen und Lebenszyklusenden sehen. Ich vertraue darauf, dass die Sprache und GC den Umfang verwalten, solange ich ausdrücklich damit einverstanden bin. Bei meinen Recherchen habe ich festgestellt, dass IOC-Container die meisten oder alle Abhängigkeiten als statisch einrichten und sie durch eine AOP-Implementierung im Hintergrund steuern. Also nichts ist transparent.
  2. Verkapselung . Was ist der Zweck der Einkapselung? Warum sollten wir so private Mitglieder behalten? Aus praktischen Gründen können Implementierer unserer API die Implementierung nicht durch Ändern des Status (der von der Eigentümerklasse verwaltet werden sollte) unterbrechen. Aus Sicherheitsgründen kann es jedoch nicht zu Injektionen kommen, die unsere Mitgliedstaaten überholen und die Validierung und Klassenkontrolle umgehen. Alles (spöttische Frameworks oder IOC-Frameworks), das Code vor der Kompilierung einfügt, um privaten Mitgliedern externen Zugriff zu ermöglichen, ist ziemlich umfangreich.
  3. Grundsatz der einheitlichen Verantwortung . An der Oberfläche scheinen IOC-Container die Dinge sauberer zu machen. Aber stellen Sie sich vor, wie Sie dasselbe ohne die Helfer-Frameworks erreichen würden. Es würden Konstruktoren mit etwa einem Dutzend Abhängigkeiten übergeben. Das bedeutet nicht, dass Sie es mit IOC-Containern verschleiern müssen, es ist eine gute Sache! Es ist ein Zeichen, Ihren Code neu zu faktorisieren und SRP zu folgen.
  4. Offen / Geschlossen . Genauso wie SRP nicht nur für Klassen gedacht ist (ich wende SRP nur für Einzelverantwortliche an, geschweige denn für Methoden). Open / Closed ist nicht nur eine übergeordnete Theorie, um den Code einer Klasse nicht zu ändern. Es ist eine Übung, die Konfiguration Ihres Programms zu verstehen und die Kontrolle darüber zu haben, was geändert und was erweitert wird. IOC-Container können die Funktionsweise Ihrer Klassen teilweise ändern, weil:

    • ein. Der Hauptcode bestimmt nicht das Auswechseln von Abhängigkeiten, sondern die Framework-Konfiguration.

    • b. Der Gültigkeitsbereich kann zu einem Zeitpunkt geändert werden, der nicht von den aufrufenden Mitgliedern gesteuert wird. Er wird stattdessen extern von einem statischen Framework festgelegt.

Die Konfiguration der Klasse ist also nicht wirklich abgeschlossen, sondern ändert sich basierend auf der Konfiguration eines Tools eines Drittanbieters.

Der Grund, warum dies eine Frage ist, ist, dass ich nicht unbedingt ein Meister aller IOC-Container bin. Und obwohl die Idee eines IOC-Containers schön ist, scheinen sie nur eine Fassade zu sein, die eine schlechte Implementierung verdeckt. Um jedoch eine externe Bereichskontrolle, den Zugriff privater Mitglieder und das langsame Laden zu erreichen, müssen viele nicht triviale, fragwürdige Dinge weitergehen. AOP ist großartig, aber die Art und Weise, wie es durch IOC-Container erreicht wird, ist auch fraglich.

Ich kann darauf vertrauen, dass C # und der .NET GC das tun, was ich von ihm erwarte. Aber ich kann nicht das gleiche Vertrauen in ein Drittanbieter-Tool setzen, das meinen kompilierten Code ändert, um Problemumgehungen für Dinge durchzuführen, die ich nicht manuell ausführen könnte.

EG: Entity Framework und andere ORMs erstellen stark typisierte Objekte und ordnen sie Datenbankentitäten zu. Außerdem bieten sie grundlegende Funktionen für die Ausführung von CRUD. Jeder kann sein eigenes ORM erstellen und die OOP / SOLID-Prinzipien weiterhin manuell befolgen. Aber diese Rahmenbedingungen helfen uns, damit wir das Rad nicht jedes Mal neu erfinden müssen. Während IOC-Container uns anscheinend dabei helfen, die OOP / SOLID-Prinzipien gezielt zu umgehen und zu vertuschen.


13
+1 Korrelation zwischen IOCund Explicitness of codeist genau das, womit ich Probleme habe. Manuelles DI kann leicht zur Pflicht werden, bringt aber zumindest den eigenständigen expliziten Programmfluss an einen Ort - "Sie bekommen, was Sie sehen, Sie sehen, was Sie bekommen". Während die Bindungen und Deklarationen von IOC-Containern leicht selbst zu einem versteckten Parallelprogramm werden können.
Eugene Podskal

6
Wie ist das überhaupt eine Frage?
Stephen

8
Dies scheint eher ein Blogeintrag als eine Frage zu sein.
Bart van Ingen Schenau

4
Ich habe eine interne Kategorie für Fragen, "zu argumentativ", was natürlich kein formeller, enger Grund ist, sondern dass ich abstimme und manchmal sogar abstimme, um "keine echte Frage" zu schließen. Es hat jedoch zwei Aspekte, und diese Frage erfüllt nur den ersten. (1) Frage stellt eine These auf und fragt, ob sie richtig ist, (2) Fragesteller antwortet auf nicht übereinstimmende Antworten und verteidigt die Ausgangsposition. Matthews Antwort widerspricht einigen Aussagen in der Frage. Wenn der Fragesteller also nicht überall herumkriecht, stufe ich dies persönlich als gutgläubige Frage ein, auch wenn es sich um die Form "Ich habe recht, nicht wahr?" Handelt.
Steve Jessop

3
@BenAaronson Danke, Ben. Ich versuche zu kommunizieren, was ich durch meine Forschung gelernt habe, und Antworten auf die Natur von IOC-Containern zu bekommen. Als Ergebnis meiner Forschung habe ich festgestellt, dass diese Prinzipien gebrochen sind. Die beste Antwort auf meine Frage würde bestätigen oder leugnen, dass IOC-Container auf irgendeine Weise Dinge tun können, die wir nicht manuell tun können, ohne die OOP / SOLID-Prinzipien zu brechen. Aufgrund Ihrer großartigen Kommentare habe ich meine Frage mit weiteren Beispielen überarbeitet.
Suamere

Antworten:


35

Ich werde Ihre Punkte numerisch durchgehen, aber zuerst sollten Sie Folgendes beachten: Verwechseln Sie nicht, wie ein Verbraucher eine Bibliothek verwendet, mit der Art und Weise, wie die Bibliothek implementiert wird . Gute Beispiele hierfür sind Entity Framework (das Sie selbst als gute Bibliothek zitieren) und ASP.NETs MVC. Beide machen eine Menge unter der Haube, zum Beispiel mit Reflektion, was absolut nicht als gutes Design angesehen werden würde, wenn Sie es durch den alltäglichen Code verbreiten würden.

Diese Bibliotheken sind absolut nicht "transparent" in ihrer Funktionsweise oder was sie hinter den Kulissen tun. Dies ist jedoch kein Nachteil, da sie gute Programmierungsprinzipien bei ihren Verbrauchern unterstützen . Wenn Sie also über eine Bibliothek wie diese sprechen, denken Sie daran, dass es für Sie als Benutzer einer Bibliothek keine Aufgabe ist, sich um deren Implementierung oder Wartung zu kümmern. Sie sollten sich nur Gedanken darüber machen, wie es dem Code hilft oder behindert, den Sie schreiben und der die Bibliothek verwendet. Kombiniere diese Konzepte nicht!

Also, um nach Punkten durchzugehen:

  1. Sofort erreichen wir, was ich nur annehmen kann, ist ein Beispiel von oben. Sie sagen, dass IOC-Container die meisten Abhängigkeiten als statisch einrichten. Nun, vielleicht gehören zu den Implementierungsdetails, wie sie funktionieren, statische Speicher IKernel. Aber das ist nicht dein Anliegen!

    Tatsächlich ist ein IoC-Container genauso explizit wie die Abhängigkeitsinjektion eines armen Mannes. (Ich werde weiterhin IoC-Container mit dem DI eines armen Mannes vergleichen, da es unfair und verwirrend wäre, sie mit keinem DI zu vergleichen. Um klar zu sein, ich verwende den DI eines armen Mannes nicht als Abwertungsmaßnahme.)

    In der DI eines armen Mannes würden Sie eine Abhängigkeit manuell instanziieren und sie dann in die Klasse einfügen, die sie benötigt. An dem Punkt, an dem Sie die Abhängigkeit erstellen, wählen Sie, was Sie damit tun möchten. Speichern Sie sie in einer Variablen mit lokalem Gültigkeitsbereich, einer Klassenvariablen oder einer statischen Variablen. Speichern Sie sie überhaupt nicht. Sie können dieselbe Instanz an viele Klassen übergeben oder für jede Klasse eine neue erstellen. Wie auch immer. Der Punkt ist, um zu sehen, was passiert, Sie schauen auf den Punkt, wahrscheinlich in der Nähe des Anwendungsstamms, an dem die Abhängigkeit erstellt wird.

    Was ist nun mit einem IoC-Container? Nun, du machst genau das gleiche! Wieder von Ninject Terminologie geht, schauen Sie auf , wo die Bindung eingerichtet ist, und ob es so etwas wie sagt InTransientScope, InSingletonScopeoder was auch immer. Wenn überhaupt, ist dies möglicherweise klarer, weil Sie den Code genau dort haben, der seinen Gültigkeitsbereich deklariert, anstatt eine Methode durchsehen zu müssen, um zu verfolgen, was mit einem Objekt passiert (ein Objekt kann auf einen Block beschränkt sein, wird aber darin mehrmals verwendet) Block oder nur einmal, zum Beispiel). Vielleicht stößt du also auf die Idee, eine Funktion im IoC-Container anstelle einer unformatierten Sprachfunktion zu verwenden, um den Umfang zu bestimmen, aber solange du deiner IoC-Bibliothek vertraust - was du auch tun solltest -, gibt es keinen wirklichen Nachteil .

  2. Ich weiß immer noch nicht genau, wovon du hier sprichst. Betrachten IoC-Container private Eigenschaften als Teil ihrer internen Implementierung? Ich weiß nicht, warum sie das tun würden, aber es ist auch nicht Ihre Aufgabe, wie eine Bibliothek, die Sie verwenden, implementiert wird.

    Oder stellen sie vielleicht eine Funktion wie das Injizieren in private Setter zur Verfügung? Ehrlich gesagt bin ich noch nie darauf gestoßen, und ich bin zweifelhaft, ob dies wirklich ein gemeinsames Merkmal ist. Aber selbst wenn es da ist, ist es ein einfacher Fall eines Werkzeugs, das missbraucht werden kann. Denken Sie daran, dass es auch ohne IoC-Container nur ein paar Zeilen Reflection-Code sind, um auf eine private Eigenschaft zuzugreifen und diese zu ändern. Es ist etwas, was Sie so gut wie nie tun sollten, aber das bedeutet nicht, dass .NET schlecht ist, um die Fähigkeit verfügbar zu machen. Wenn jemand ein Werkzeug so offensichtlich und wild missbraucht, ist es die Schuld der Person, nicht die des Werkzeugs.

  3. Der letzte Punkt ist hier ähnlich wie bei 2. In den meisten Fällen verwenden IoC-Container den Konstruktor. Die Setter-Injektion wird unter ganz bestimmten Umständen angeboten, unter denen aus bestimmten Gründen die Konstruktor-Injektion nicht verwendet werden kann. Wer ständig Setter-Injection einsetzt, um zu verbergen, wie viele Abhängigkeiten übergeben werden, verschwindet massiv aus dem Werkzeug. Das ist NICHT die Schuld des Werkzeugs, sondern ihre.

    Wenn dies ein wirklich einfacher Fehler war, den ich unschuldig begangen habe und den IoC-Container ermutigen, dann haben Sie vielleicht Recht. Es wäre, als würde man jedes Mitglied meiner Klasse öffentlich machen und dann anderen die Schuld geben, wenn sie Dinge ändern, die sie nicht sollten, oder? Wer jedoch Verstöße gegen die SRP mit Setter Injection vertuscht, ignoriert die grundlegenden Designprinzipien absichtlich oder kennt sie überhaupt nicht. Es ist unvernünftig, den IoC-Container dafür verantwortlich zu machen.

    Dies gilt insbesondere , da dies auch mit dem DI eines armen Mannes genauso einfach möglich ist:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    Diese Sorge scheint also völlig orthogonal zu sein, ob ein IoC-Container verwendet wird oder nicht.

  4. Ändern, wie Ihre Klassen zusammenarbeiten, ist kein Verstoß gegen OCP. Wenn dies der Fall wäre, würde jede Abhängigkeitsinversion eine Verletzung von OCP fördern. Wenn dies der Fall wäre, würden sie nicht beide das gleiche SOLID-Akronym haben!

    Außerdem haben weder a) noch b) etwas mit OCP zu tun. Ich weiß nicht einmal, wie ich dies in Bezug auf OCP beantworten soll.

    Das einzige, was ich vermuten kann, ist, dass Sie glauben, OCP habe etwas damit zu tun, dass das Verhalten zur Laufzeit nicht geändert wird oder von wo aus im Code der Lebenszyklus von Abhängigkeiten gesteuert wird. Es ist nicht. Bei OCP muss kein vorhandener Code geändert werden, wenn Anforderungen hinzugefügt oder geändert werden. Es geht nur darum , Code zu schreiben , nicht darum, wie der Code, den Sie bereits geschrieben haben, zusammengeklebt ist (obwohl lose Kopplung natürlich ein wichtiger Teil des Erreichens von OCP ist).

Und eine letzte Sache, die Sie sagen:

Aber ich kann nicht das gleiche Vertrauen in ein Drittanbieter-Tool setzen, das meinen kompilierten Code ändert, um Problemumgehungen für Dinge durchzuführen, die ich nicht manuell ausführen könnte.

Ja, das kannst du . Es gibt absolut keinen Grund zu der Annahme, dass diese Tools, auf die sich eine Vielzahl von Projekten stützt, fehlerhafter sind oder unerwartetem Verhalten unterliegen als jede andere Bibliothek von Drittanbietern.

Nachtrag

Ich habe gerade bemerkt, dass Ihre Intro-Absätze auch eine Adressierung gebrauchen könnten. Sie sagen sardonisch, dass IoC-Container "keine geheime Technik verwenden, von der wir noch nie gehört haben", um chaotischen, duplikationsanfälligen Code zum Erstellen eines Abhängigkeitsdiagramms zu vermeiden. Und Sie haben vollkommen recht, sie gehen diese Dinge tatsächlich mit denselben grundlegenden Techniken an, wie wir Programmierer es immer tun.

Lassen Sie mich Ihnen ein hypothetisches Szenario erläutern. Als Programmierer stellen Sie eine große Anwendung zusammen, und an dem Einstiegspunkt, an dem Sie Ihr Objektdiagramm erstellen, stellen Sie fest, dass Sie einen ziemlich unordentlichen Code haben. Es gibt eine ganze Reihe von Klassen, die immer wieder verwendet werden, und jedes Mal, wenn Sie eine dieser Klassen erstellen, müssen Sie die gesamte Kette von Abhängigkeiten unter ihnen erneut erstellen. Außerdem haben Sie keine aussagekräftige Möglichkeit, den Lebenszyklus von Abhängigkeiten zu deklarieren oder zu steuern, außer mit benutzerdefiniertem Code für jede Abhängigkeit. Ihr Code ist unstrukturiert und voller Wiederholungen. Dies ist die Unordnung, über die Sie in Ihrem Intro-Absatz sprechen.

Also fangen Sie zuerst an, ein Bit umzugestalten, bei dem ein wiederholter Code so strukturiert ist, dass Sie ihn in Hilfsmethoden auslagern und so weiter. Aber dann fangen Sie an zu überlegen - ist dies ein Problem, das ich vielleicht allgemein angehen könnte , das nicht spezifisch für dieses bestimmte Projekt ist, das Ihnen aber bei all Ihren zukünftigen Projekten helfen könnte?

Sie setzen sich also hin und überlegen, ob es eine Klasse geben soll, die Abhängigkeiten auflöst. Und Sie skizzieren, welche öffentlichen Methoden benötigt werden:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Bind"Wenn Sie ein Konstruktorargument vom Typ sehen interfaceType, übergeben Sie eine Instanz von concreteType". Der zusätzliche singletonParameter gibt an, ob concreteTypejedes Mal dieselbe Instanz verwendet oder immer eine neue erstellt werden soll.

ResolveIch werde einfach versuchen, Tmit jedem Konstruktor zu konstruieren, den es finden kann, dessen Argumente alle Typen sind, die zuvor gebunden wurden. Es kann sich auch rekursiv aufrufen, um die Abhängigkeiten vollständig aufzulösen. Wenn eine Instanz nicht aufgelöst werden kann, weil nicht alles gebunden wurde, wird eine Ausnahme ausgelöst.

Sie können versuchen, dies selbst zu implementieren, und Sie werden feststellen, dass Sie ein wenig Nachdenken und Zwischenspeichern für die Bindungen benötigen, wenn dies singletonzutrifft, aber auf keinen Fall drastisch oder schrecklich. Und wenn Sie fertig sind , haben Sie den Kern Ihres eigenen IoC-Containers! Ist es wirklich so beängstigend? Der einzige wirkliche Unterschied zwischen diesem und Ninject oder StructureMap oder Castle Windsor oder was auch immer Sie bevorzugen, ist, dass diese viel mehr Funktionen haben, um die (vielen!) Anwendungsfälle abzudecken, in denen diese Basisversion nicht ausreicht. Was Sie jedoch im Herzen haben, ist die Essenz eines IoC-Containers.


Danke Ben. Ich habe ehrlich gesagt nur ungefähr ein Jahr mit IOC Containers gearbeitet, und obwohl ich es nicht tue, konzentrieren sich die Tutorials und Kollegen um mich herum wirklich auf die Immobilieninjektion (einschließlich privat). Es ist verrückt und bringt alle Punkte auf den Punkt, die ich mache. Aber auch wenn sich andere in dieser Situation befinden, besteht das Beste, was sie tun können, darin, mehr darüber zu lernen, wie IOC-Container funktionieren sollten, um Prinzipien zu befolgen, und diese Prinzip-Breaker in Code-Reviews herauszustellen. Allerdings ... (Fortsetzung)
Suamere

Mein größtes Anliegen ist nicht unbedingt in den OOP / SOLID-Grundsätzen aufgeführt, und das ist ein eindeutiger Beweis für den Umfang. Ich noch werfen wir Block, Local, Module und Global Level Scope Verständnis aus dem Fenster für ein Schlüsselwort. Die ganze Idee, Blöcke und Deklarationen zu verwenden, die bestimmen, wann etwas aus dem Rahmen fällt oder entsorgt wird, ist für mich riesig, und ich denke, das war der schrecklichste Teil von IOC Containers für mich, um dieses sehr grundlegende Stück Entwicklung durch ein Erweiterungsschlüsselwort zu ersetzen .
Suamere

Deshalb habe ich Ihre Antwort als Antwort markiert, weil sie das Herzstück dieser Frage wirklich anspricht. Und so wie ich es lese, muss man einfach darauf vertrauen, was IOC-Container im Verborgenen tun, weil es alle anderen tun. Und was die Eröffnungen für möglichen Missbrauch und die elegante Art und Weise betrifft, müssen sich Shop- und Code-Überprüfungen darum kümmern und sie sollten ihre gebührende Sorgfalt darauf verwenden, gute Entwicklungsprinzipien zu befolgen.
Suamere

2
In Bezug auf 4 steht Dependency Injection nicht im Akronym SOLID . 'D' = 'Dependency Inversion Principle', ein völlig verwandtes Konzept.
Astreltsov

@astr Guter Punkt. Ich habe "Injection" durch "Inversion" ersetzt, da ich denke, dass ich das eigentlich schreiben wollte. Das ist immer noch eine leichte Vereinfachung, da die Abhängigkeitsinversion nicht unbedingt mehrere konkrete Implementierungen impliziert, aber ich denke, die Bedeutung ist klar genug. Es wäre gefährlich, sich auf eine polymorphe Abstraktion zu verlassen, wenn das Auswechseln von Implementierungen eine OCP-Verletzung wäre.
Ben Aaronson

27

Verstoßen IOC-Container nicht gegen viele dieser Prinzipien?

In der Theorie? Nein, IoC-Container sind nur ein Werkzeug. Sie können Objekte haben, die eine einzige Verantwortung haben, gut getrennte Schnittstellen, deren Verhalten nach dem Schreiben nicht mehr geändert werden kann usw.

In der Praxis? Absolut.

Ich meine, ich habe nicht wenige Programmierer gehört, die sagen, dass sie IoC-Container speziell verwenden , um Ihnen die Verwendung einer bestimmten Konfiguration zu ermöglichen, um das Verhalten des Systems zu ändern - was gegen das Open / Closed-Prinzip verstößt. Früher gab es Witze über das Codieren in XML, weil 90% ihrer Arbeit darin bestand, die Spring-Konfiguration zu reparieren. Und Sie haben sicherlich Recht, dass das Ausblenden der Abhängigkeiten (was zugegebenermaßen nicht bei allen IoC-Containern der Fall ist) eine schnelle und einfache Möglichkeit ist, hochgradig gekoppelten und sehr fragilen Code zu erstellen.

Schauen wir es uns anders an. Betrachten wir eine sehr einfache Klasse mit einer einzigen grundlegenden Abhängigkeit. Es hängt von einem Action, einem Delegaten oder einem Funktor ab, der keine Parameter akzeptiert und zurückgibt void. Was ist die Verantwortung dieser Klasse? Das kannst du nicht sagen. Ich könnte diese Abhängigkeit so klar benennen, dass CleanUpActionman denkt, sie macht das, was man will. Sobald IoC ins Spiel kommt, wird es alles . Jeder kann in die Konfiguration gehen und Ihre Software modifizieren, um so ziemlich beliebige Dinge zu tun.

"Wie unterscheidet sich das von der Übergabe Actionan den Konstruktor?" du fragst. Dies ist anders, da Sie nicht ändern können, was nach der Bereitstellung der Software (oder zur Laufzeit!) An den Konstruktor übergeben wird. Dies ist anders, da Ihre Komponententests (oder die Komponententests Ihrer Kunden) die Szenarien abgedeckt haben, in denen der Delegat an den Konstruktor übergeben wird.

"Aber das ist ein falsches Beispiel! Meine Sachen erlauben keine Verhaltensänderungen!" du rufst aus Tut mir leid, dass ich es dir sage, aber es tut mir leid. Es sei denn, die Schnittstelle, die Sie injizieren, besteht nur aus Daten. Und Ihre Schnittstelle ist nicht nur Daten, denn wenn es nur Daten wären, würden Sie ein einfaches Objekt erstellen und dieses verwenden. Sobald Sie eine virtuelle Methode in Ihrem Injektionspunkt haben, lautet der Vertrag Ihrer Klasse mit IoC "Ich kann alles ".

"Wie unterscheidet sich das von der Verwendung von Skripten, um Dinge voranzutreiben? Das ist vollkommen in Ordnung." einige von euch fragen. Es ist nicht anders. Aus Sicht der Programmgestaltung gibt es keinen Unterschied. Sie verlassen Ihr Programm, um beliebigen Code auszuführen. Und sicher, das wird schon seit Ewigkeiten in Spielen gemacht. Es ist in Tonnen von Systemadministrationstools getan. Ist es OOP? Nicht wirklich.

Es gibt keine Entschuldigung für ihre gegenwärtige Allgegenwart. IoC-Container haben einen festen Zweck: Ihre Abhängigkeiten zusammenzufügen, wenn Sie über so viele Kombinationen verfügen, oder wenn sie sich so schnell verschieben, dass es unpraktisch ist, diese Abhängigkeiten explizit anzugeben. Daher passen nur sehr wenige Unternehmen in dieses Szenario.


3
Richtig. Und viele Geschäfte behaupten, ihr Produkt sei so komplex, dass sie in diese Kategorie fallen, wenn sie es wirklich nicht tun. Wie ist die Wahrheit mit vielen Szenarien.
Suamere

12
Aber IoC ist genau das Gegenteil von dem, was Sie gesagt haben. Es setzt Open / Closeness des Programms ein. Wenn Sie das Verhalten des gesamten Programms ändern können, ohne den Code selbst ändern zu müssen.
Euphorischer

6
@Euphoric - zur Erweiterung offen, zur Änderung geschlossen. Wenn Sie das Verhalten des gesamten Programms ändern können, ist es nicht für Änderungen geschlossen, oder? Die IoC-Konfiguration ist nach wie vor Ihre Software. Es handelt sich immer noch um Code, der von Ihren Klassen für die Arbeit verwendet wird - selbst wenn er in XML und nicht in Java oder C # oder einem anderen Format vorliegt.
Telastyn

4
@Telastyn Das "zur Änderung geschlossen" bedeutet, dass es nicht notwendig sein sollte, den Code zu ändern (zu modifizieren), um das Verhalten eines Ganzen zu ändern. Bei jeder externen Konfiguration können Sie einfach auf eine neue DLL verweisen und das Verhalten einer vollständigen Änderung feststellen.
Euphorischer

12
@Telastyn Das sagt das Open / Closed-Prinzip nicht aus. Andernfalls würde eine Methode, die Parameter akzeptiert, gegen OCP verstoßen, da ich ihr Verhalten durch Übergabe anderer Parameter "ändern" könnte. Ebenso würde es unter Ihrer Version von OCP in direktem Konflikt mit DI stehen, was es ziemlich seltsam machen würde, dass beide im SOLID-Akronym erscheinen.
Ben Aaronson

14

Verstößt die Verwendung eines IoC-Containers notwendigerweise gegen die OOP / SOLID-Prinzipien? Nein .

Können Sie IoC-Container so verwenden, dass sie gegen die OOP / SOLID-Prinzipien verstoßen? Ja .

IoC-Container sind nur Frameworks zum Verwalten von Abhängigkeiten in Ihrer Anwendung.

Sie sagten Lazy Injection, Property Setter und einige andere Funktionen, die ich noch nicht nutzen wollte, und denen ich zustimme, können Ihnen ein weniger als optimales Design verleihen. Die Verwendung dieser spezifischen Funktionen ist jedoch auf Einschränkungen in der Technologie zurückzuführen, in der Sie IoC-Container implementieren.

Bei ASP.NET-WebForms müssen Sie beispielsweise die Eigenschaftsinjektion verwenden, da es nicht möglich ist, eine Instanz zu erstellen Page. Dies ist verständlich, da WebForms niemals unter Berücksichtigung strenger OOP-Paradigmen entwickelt wurde.

ASP.NET MVC andererseits ist es durchaus möglich, einen IoC-Container mit der sehr OOP / SOLID-freundlichen Konstruktorinjektion zu verwenden.

In diesem Szenario (mithilfe der Konstruktorinjektion) werden meines Erachtens aus den folgenden Gründen die SOLID-Prinzipien gefördert:

  • Prinzip der Einzelverantwortung - Mit vielen kleineren Klassen können diese Klassen sehr spezifisch sein und einen Grund für die Existenz haben. Die Verwendung eines IoC-Containers erleichtert die Organisation erheblich.

  • Offen / Geschlossen - Hier können Sie die Funktionalität einer Klasse erweitern, indem Sie verschiedene Abhängigkeiten einfügen. Mit Abhängigkeiten, die Sie einfügen, können Sie das Verhalten dieser Klasse ändern. Ein gutes Beispiel ist das Ändern der Klasse, die Sie injizieren, indem Sie einen Decorator schreiben, der Caching oder Protokollierung hinzufügt. Sie können die Implementierungen Ihrer Abhängigkeiten vollständig ändern, wenn Sie sie in einer geeigneten Abstraktionsebene schreiben.

  • Liskov-Substitutionsprinzip - Nicht wirklich relevant

  • Prinzip der Schnittstellentrennung - Wenn Sie möchten, können Sie einer konkreten Zeit mehrere Schnittstellen zuweisen. Jeder IoC-Behälter, der ein Körnchen Salz enthält, kann dies problemlos tun.

  • Abhängigkeitsinversion - Mit IoC-Containern können Sie die Abhängigkeitsinversion auf einfache Weise durchführen.

Sie definieren eine Reihe von Abhängigkeiten und deklarieren die Beziehungen und Gültigkeitsbereiche sowie den Bing Bang Boom: Sie haben auf magische Weise ein Addon, das Ihren Code oder IL irgendwann ändert und die Dinge zum Laufen bringt. Das ist weder explizit noch transparent.

Es ist sowohl explizit als auch transparent. Sie müssen Ihrem IoC-Container mitteilen, wie Abhängigkeiten verkabelt und die Lebensdauer von Objekten verwaltet werden sollen. Dies kann entweder durch Konvention, durch Konfigurationsdateien oder durch Code geschehen, der in den Hauptanwendungen Ihres Systems geschrieben wurde.

Ich bin damit einverstanden, dass beim Schreiben oder Lesen von Code mithilfe von IOC-Containern ein geringfügig uneleganter Code entfernt wird.

Dies ist der ganze Grund für OOP / SOLID, um Systeme verwaltbar zu machen. Die Verwendung eines IoC-Containers entspricht diesem Ziel.

Der Schreiber dieser Klasse sagt: "Wenn wir keinen IOC-Container verwenden würden, hätte der Konstruktor ein Dutzend Parameter !!! Das ist schrecklich!"

Eine Klasse mit 12 Abhängigkeiten ist wahrscheinlich eine SRP-Verletzung, egal in welchem ​​Kontext Sie sie verwenden.


2
Hervorragende Antwort. Darüber hinaus unterstützen meines Erachtens alle wichtigen IOC-Container auch AOP, was bei OCP sehr hilfreich sein kann. Für Querschnittsthemen ist AOP normalerweise eine OCP-freundlichere Route als ein Dekorateur.
Ben Aaronson

2
@BenAaronson Wenn man bedenkt, dass AOP Code in eine Klasse einfügt, würde ich es nicht als OCP-freundlicher bezeichnen. Sie erzwingen das Öffnen und Ändern einer Klasse zur Kompilierung / Laufzeit.
Doval

1
@Doval Ich verstehe nicht, wie Code in eine Klasse eingefügt wird. Hinweis Ich spreche vom Castle DynamicProxy-Stil, bei dem Sie einen Abfangjäger haben, und nicht vom IL-Webstil. Ein Interceptor ist jedoch im Grunde genommen nur ein Dekorateur, der einige Überlegungen anstellt, damit er sich nicht an eine bestimmte Schnittstelle koppeln muss.
Ben Aaronson

"Abhängigkeitsinversion - Mit IoC-Containern können Sie die Abhängigkeitsinversion auf einfache Weise durchführen" - dies ist jedoch nicht der Fall. Sie nutzen einfach etwas, das von Vorteil ist, unabhängig davon, ob Sie über IoC verfügen oder nicht. Gleiches gilt für andere Prinzipien.
Den

Ich verstehe die Logik der Erklärung nicht, dass etwas nicht schlecht ist, weil es ein Rahmen ist. ServiceLocators sind ebenfalls Frameworks und gelten als schlecht :).
Ipavlu

5

Brechen nicht beide IOC-Container viele OOP / SOLID-Prinzipien und erlauben es den Codierern, diese zu brechen?

Mit dem staticSchlüsselwort in C # können Entwickler viele OOP / SOLID-Prinzipien brechen, aber es ist unter den richtigen Umständen immer noch ein gültiges Werkzeug.

IoC-Container ermöglichen gut strukturierten Code und verringern die kognitive Belastung für Entwickler. Ein IoC-Container kann verwendet werden, um das Befolgen der SOLID-Prinzipien erheblich zu vereinfachen. Es kann zwar missbraucht werden, aber wenn wir die Verwendung eines missbräuchlichen Tools ausschließen würden, müssten wir die Programmierung ganz aufgeben.

Während dieser Vorwurf einige gültige Punkte über schlecht geschriebenen Code aufwirft, ist dies nicht unbedingt eine Frage und auch nicht besonders nützlich.


Ich verstehe unter IOC-Containern, dass der kompilierte Code geändert wird, um den Umfang, die Faulheit und die Zugänglichkeit zu verwalten, um Dinge zu tun, die OOP / SOLID beschädigen, aber diese Implementierung in einem netten Rahmen verbergen. Aber ja, es öffnet zusätzlich andere Türen für eine falsche Verwendung. Die beste Antwort auf meine Frage wäre eine Klarstellung darüber, wie IOC-Container implementiert werden, die durch meine eigene Forschung ergänzt oder negiert wird.
Suamere

2
@Suamere - Ich denke, Sie verwenden möglicherweise eine zu weit gefasste Definition von IOC. Der von mir verwendete Code ändert keinen kompilierten Code. Es gibt viele andere Nicht-IOC-Tools, mit denen auch kompilierter Code geändert werden kann. Vielleicht sollte Ihr Titel eher so lauten: "Bricht das Ändern von kompiliertem Code ab ...".
Cerad

@Suamere Ich bin nicht so erfahren mit IoC, aber ich habe noch nie davon gehört, dass IoC kompilierten Code ändert. Geben Sie bitte an, über welche Plattformen / Sprachen / Frameworks Sie sprechen.
Euphorischer

1
"Ein IoC-Container kann verwendet werden, um das Befolgen der SOLID-Prinzipien erheblich zu vereinfachen." - Können Sie bitte näher darauf eingehen? Es wäre hilfreich zu erklären, wie spezifisch es bei jedem Prinzip hilft. Und natürlich ist das Nutzen von etwas nicht dasselbe wie das Helfen von etwas (z. B. DI).
Den

1
@Euphoric Ich weiß nichts über die .net-Frameworks, über die suamere gesprochen hat, aber Spring ändert den Code unter der Haube. Das offensichtlichste Beispiel ist die Unterstützung für methodenbasierte Injection (wobei eine abstrakte Methode in Ihrer Klasse außer Kraft gesetzt wird, um eine von ihr verwaltete Abhängigkeit zurückzugeben). Darüber hinaus werden in großem Umfang automatisch generierte Proxys verwendet, um Ihren Code zu verpacken und zusätzliche Verhaltensweisen bereitzustellen (z. B. zum automatischen Verwalten von Transaktionen). Vieles davon hängt jedoch nicht mit seiner Rolle als DI-Framework zusammen.
Jules

3

Ich sehe mehrere Fehler und Missverständnisse in Ihrer Frage. Der erste wichtige Punkt ist, dass Ihnen die Unterscheidung zwischen Eigenschafts- / Feldinjektion, Konstruktorinjektion und Service-Locator fehlt. Es gab viele Diskussionen (z. B. Flammenkrieg) darüber, wie man etwas richtig macht. In Ihrer Frage scheinen Sie nur die Eigenschaftsinjektion anzunehmen und die Konstruktorinjektion zu ignorieren.

Zweitens gehen Sie davon aus, dass sich IoC-Container auf die Gestaltung Ihres Codes auswirken. Sie müssen nicht oder zumindest nicht das Design Ihres Codes ändern, wenn Sie IoC verwenden. Ihre Probleme mit Klassen mit vielen Konstruktoren sind darauf zurückzuführen, dass IoC echte Probleme mit Ihrem Code versteckt (möglicherweise eine Klasse, die die SRP unterbricht), und dass versteckte Abhängigkeiten einer der Gründe sind, warum Leute von der Verwendung der Eigenschaftsinjektion und des Service-Locators abraten.

Ich verstehe nicht, wo das Problem mit dem Open-Closed-Prinzip liegt, also werde ich es nicht kommentieren. Ihnen fehlt jedoch ein Teil von SOLID, der großen Einfluss darauf hat: das Prinzip der Abhängigkeitsinversion. Wenn Sie DIP folgen, werden Sie feststellen, dass das Invertieren einer Abhängigkeit eine Abstraktion einführt, deren Implementierung aufgelöst werden muss, wenn eine Instanz einer Klasse erstellt wird. Mit diesem Ansatz werden Sie am Ende viele Klassen haben, für deren ordnungsgemäße Funktion mehrere Schnittstellen realisiert und bei der Erstellung bereitgestellt werden müssen. Wenn Sie diese Abhängigkeiten dann manuell bereitstellen würden, müssten Sie viel zusätzliche Arbeit leisten. IoC-Container erleichtern diese Arbeit und ermöglichen es Ihnen, sie zu ändern, ohne das Programm neu kompilieren zu müssen.

Die Antwort auf Ihre Frage lautet also nein. IoC-Container verstoßen nicht gegen die OOP-Designprinzipien. Ganz im Gegenteil, sie lösen Probleme, die durch das Befolgen der SOLID-Prinzipien (insbesondere des Dependency-Inversion-Prinzips) verursacht werden.


Ich habe kürzlich versucht, IoC (Autofac) auf ein nicht triviales Projekt anzuwenden. Mir wurde klar, dass ich ähnliche Aufgaben mit nicht-sprachlichen Konstrukten (new () vs API) ausführen musste: Festlegen, was in Schnittstellen und was in konkrete Klassen aufgelöst werden soll, Festlegen, was eine Instanz und was ein Singleton sein soll, Stellen Sie sicher, dass die Eigenschaftsinjektion ordnungsgemäß funktioniert, um eine zirkuläre Abhängigkeit zu verringern. Beschlossen, aufzugeben. Funktioniert jedoch gut mit Controllern in ASP MVC.
Den

@Den Ihr Projekt wurde offensichtlich nicht mit Blick auf die Abhängigkeitsinversion entworfen. Und nirgendwo habe ich gesagt, dass die Verwendung von IoC trivial ist.
Euphorischer

Eigentlich ist das die Sache - ich benutze Dependency Inversion von Anfang an. Darüber hinaus ist es meine größte Sorge, dass die IoC das Design beeinflusst. Warum sollte ich beispielsweise überall Schnittstellen verwenden, um die IoC zu vereinfachen? Siehe auch den oberen Kommentar zur Frage.
Den

@Den Sie technisch nicht "brauchen", um Schnittstellen zu verwenden. Schnittstellen helfen beim Verspotten für Unit-Tests. Sie können auch Dinge markieren, die Sie zum Verspotten von virtuellen Objekten benötigen.
Fred
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.