Verwenden Sie korrekt die Abhängigkeitsinjektion

8

Ich und zwei weitere Kollegen versuchen zu verstehen, wie man ein Programm am besten gestaltet. Zum Beispiel habe ich eine Schnittstelle ISoda und mehrere Klassen, die diese Schnittstelle implementieren wie Coke , Pepsi , DrPepper , etc ....

Mein Kollege sagt, dass es am besten ist, diese Elemente wie ein Schlüssel / Wert-Paar in eine Datenbank zu stellen. Zum Beispiel:

%Vor%

... dann XML-Konfigurationsdateien, die die Eingabe dem richtigen Schlüssel zuordnen, die Datenbank nach dem Schlüssel abfragen und dann das Objekt erstellen.

Ich habe keine spezifischen Gründe, aber ich habe das Gefühl, dass dies ein schlechtes Design ist, aber ich weiß nicht, was ich sagen soll oder wie ich dagegen argumentieren kann.

Mein zweiter Kollege schlägt vor, dass wir jede dieser Klassen verwalten. Also würde die Eingabe im Grunde eine switch-Anweisung durchlaufen, etwas ähnliches:

%Vor%

Das scheint mir etwas besser zu sein, aber ich denke immer noch, dass es einen besseren Weg dafür gibt. Ich habe die letzten Tage auf IoC-Containern gelesen und es scheint eine gute Lösung zu sein. Ich bin jedoch immer noch sehr neu in Dependency-Injection- und IoC-Containern, daher weiß ich nicht, wie ich dafür richtig argumentieren kann. Oder bin ich vielleicht der falsche und es gibt einen besseren Weg? Wenn ja, kann jemand eine bessere Methode vorschlagen?

Welche Argumente kann ich an den Tisch bringen, um meine Kollegen davon zu überzeugen, eine andere Methode auszuprobieren? Was sind die Vor- / Nachteile? Warum sollten wir es in einer Richtung machen?

Leider sind meine Kollegen sehr resistent gegen Veränderungen, also versuche ich herauszufinden, wie ich sie überzeugen kann.

Bearbeiten:

Scheint so, als ob meine Frage ein bisschen schwer zu verstehen ist. Was wir herausfinden wollen, ist, wie man am besten eine Anwendung entwickelt, die mehrere Schnittstellen verwendet und viele konkrete Typen enthält, die diese Schnittstellen implementieren.

Ja, ich weiß, dass das Speichern dieses Materials in der Datenbank eine schlechte Idee ist, aber wir wissen einfach keinen besseren Weg. Deshalb frage ich wie wir es besser machen.

%Vor%

Wie implementieren wir GetSoda korrekt? Sollten wir das gesamte Design überdenken? Wenn es eine schlechte Idee ist, magische Saiten wie diese zu umgehen, wie sollen wir das dann machen?

Benutzereingaben wie "Diät-Cola", "Cola", "Cola Lime" oder was auch immer, würde einen Typ von Coke instanziieren, sodass mehrere Keywords auf einen einzigen Typ abgebildet werden.

Viele Leute haben gesagt, dass der oben genannte Code schlecht ist. Ich weiß, dass es schlecht ist. Ich suche nach Gründen, um meinen Kollegen zu präsentieren und zu argumentieren, warum es schlecht ist und warum wir uns ändern müssen.

Ich entschuldige mich, wenn diese Erklärung immer noch schwer zu verstehen ist. Es ist schwer für mich, die Situation zu beschreiben, weil ich nicht wirklich verstehe, wie man es in Worte fasst.

    
Rune 04.04.2010, 02:34
quelle

6 Antworten

7

Im Allgemeinen ist es objektorientierter, Objekte umzuleiten, die Konzepte kapseln, als um Strings herumzugehen. Das heißt, es gibt viele Fälle (insbesondere wenn es um UI geht), wenn Sie sparse Daten (wie Strings) in richtige Domain-Objekte übersetzen müssen.

Als ein Beispiel müssen Sie Benutzereingaben in Form einer Zeichenfolge in eine ISoda-Instanz übersetzen.

Der übliche, lose gekoppelte Weg dazu ist die Verwendung einer abstrakten Fabrik . Definiere es so:

%Vor%

Ihr Konsument kann nun eine Abhängigkeit von ISodaFactory nehmen und diese wie folgt verwenden:

%Vor%

Beachten Sie, dass die Kombination der Guard-Klausel und des readonly -Schlüssels die Invarianten der SodaConsumer-Klasse garantiert, wodurch Sie die Abhängigkeit verwenden können, ohne sich Sorgen zu machen, dass sie null sein könnte.

    
Mark Seemann 04.04.2010 09:41
quelle
4

Die beste Einführung in die Abhängigkeitsinjektion, auf die ich gestoßen bin, ist die schnelle Reihe von Artikeln, die einen Rundgang durch die Konzepte bilden auf der Wiki-Seite Ninject .

Sie haben nicht wirklich viel Hintergrundwissen über das, was Sie erreichen wollen, gegeben, aber es sieht so aus, als wollten Sie eine "Plug-in" -Architektur entwerfen. Wenn dies der Fall ist - Ihr Gefühl ist richtig - beide Designs sind in einem Wort, schrecklich. Die erste ist relativ langsam (Datenbank und Reflexion?), Hat massive unnötige Abhängigkeiten von einer Datenbankschicht und wird nicht mit Ihrem Programm skalieren, da jede neue Klasse in die Datenbank eingegeben werden muss . Was ist los mit der Verwendung von Reflektion?

Der zweite Ansatz ist ungefähr so ​​unausführbar, wie er kommt. Jedes Mal, wenn Sie eine neue Klasse einführen, muss alter Code aktualisiert werden, und etwas muss über jedes Objekt, das Sie einstecken, "wissen", was es Dritten unmöglich macht, Plugins zu entwickeln. Die Kopplung, die Sie hinzufügen, selbst mit einem Locator-Muster, bedeutet, dass Ihr Code nicht so flexibel ist, wie er sein könnte.

Dies kann eine gute Gelegenheit sein, die Abhängigkeitsinjektion zu verwenden. Lesen Sie die Artikel, die ich verlinkt habe. Die Schlüsselidee ist, dass das Abhängigkeitsinjektions-Framework eine Konfigurationsdatei verwendet, um die von Ihnen angegebenen Klassen dynamisch zu laden. Dies macht den Austausch von Komponenten Ihres Programms sehr einfach und unkompliziert.

    
womp 04.04.2010 03:10
quelle
4

Dies ist das Muster, das ich normalerweise verwende, wenn ich die korrekte Klasse anhand eines Namens oder anderer serialisierter Daten instanziieren muss:

%Vor%

Beachten Sie, dass Sie immer noch viele ISodaCreator -Implementierungen vornehmen müssen und alle Implementierungen sammeln müssen, die an den SodaFactory -Konstruktor übergeben werden sollen. Um die damit verbundene Arbeit zu reduzieren, könnten Sie einen einzigen generischen Soda Creator basierend auf der Reflektion erstellen:

%Vor%

und scanne dann die ausführende Assembly (oder eine andere Sammlung von Assemblys), um alle Sodaarten zu finden und ein SodaFactory wie folgt zu erstellen:

%Vor%

Beachten Sie, dass meine Antwort die von Mark Seemann ergänzt: Er sagt Ihnen, dass Sie Kunden eine abstrakte ISodaFactory verwenden sollen, damit sie sich nicht darum kümmern müssen, wie soda factory funktioniert. Meine Antwort ist eine Beispielimplementierung solch einer Sodafabrik.

    
Wim Coenen 04.04.2010 10:53
quelle
3

Ich werde ehrlich sein, und ich weiß, dass andere nicht übereinstimmen, aber ich finde die abstrahierende Klasseninstanziierung in XML oder eine Datenbank als eine schreckliche Idee. Ich bin ein Fan davon, Code klar zu machen, auch wenn er etwas Flexibilität opfert. Die Schattenseiten, die ich sehe, sind:

  • Es ist schwer, durch Code zu suchen und zu finden, wo Objekte erstellt werden.
  • Andere Personen, die an dem Projekt arbeiten, müssen Ihr proprietäres Format zum Instanziieren von Objekten verstehen.
  • Bei kleinen Projekten verbringen Sie mehr Zeit mit der Implementierung der Abhängigkeitsinjektion, sodass Sie tatsächlich die Klassen wechseln.
  • Sie können erst zur Laufzeit feststellen, was erstellt wird. Das ist ein Albtraum für das Debuggen, und Ihr Debugger wird Sie auch nicht mögen.

Ich bin eigentlich ein bisschen müde davon, von dieser speziellen Programmier-Modeerscheinung zu hören. Es fühlt sich an und sieht hässlich aus, und es löst ein Problem, das nur wenige Projekte lösen müssen. Ernsthaft, wie oft werden Sie Sodas aus diesem Code hinzufügen und entfernen. Es sei denn, es ist praktisch jedes Mal, wenn Sie kompilieren, dann gibt es so wenig Vorteil mit so viel zusätzlicher Kompliziertheit.

Nur zur Klarstellung, ich denke, dass Inversion der Kontrolle eine gute Designidee sein kann. Ich mag es einfach nicht, Code extern eingebettet nur für Instanziierung, in XML oder anders zu haben.

Die Switch-Case-Lösung ist in Ordnung, da dies eine Möglichkeit zur Implementierung des Factory-Patterns darstellt. Die meisten Programmierer werden nicht schwer zu verstehen sein. Es ist außerdem übersichtlicher organisiert, als Unterklassen rund um Ihren Code zu instanziieren.

    
Kai 04.04.2010 03:20
quelle
3

Jede DI-Bibliothek hat hierfür ihre eigene Syntax. Ninject sieht folgendermaßen aus:

%Vor%

Natürlich wird es ziemlich mühsam, die Mappings beizubehalten. Wenn ich also ein solches System habe, dann mache ich normalerweise eine reflektionsbasierte Registrierung ähnlich wie bei dieser :

%Vor%

Etwas mehr Code, aber Sie müssen ihn nie aktualisieren, er wird sich selbst registrieren.

Oh, und du benutzt es so:

%Vor%

Offensichtlich ist dies spezifisch für eine einzelne DI-Bibliothek (Ninject), aber alle unterstützen die Vorstellung von Variablen oder Parametern, und es gibt äquivalente Muster für alle (Autofac, Windsor, Unity, etc.)

Also die kurze Antwort auf Ihre Frage ist im Grunde ja, DI kann dies tun , und es ist ein ziemlich guter Ort, um diese Art von Logik zu haben, obwohl keiner der Code-Beispiele in Ihrem Original Frage hat eigentlich etwas mit DI zu tun (die zweite ist nur eine Fabrikmethode, und die erste ist ... nun, Enterprise).

    
Aaronaught 04.04.2010 03:43
quelle
3

Ich denke, das Problem ist nicht einmal eine Fabrik oder was auch immer; Es ist deine Klassenstruktur.

Verhält sich DrPepper anders als Pepsi ? Und Coke ? Für mich hört sich das so an, als würden Sie Interfaces nicht richtig benutzen.

Abhängig von der Situation würde ich eine Klasse Soda mit einer Eigenschaft BrandName oder ähnlich machen. Die BrandName würde auf DrPepper oder Coke gesetzt werden, entweder durch eine enum oder eine string (die erste scheint eine bessere Lösung für Ihren Fall zu sein).

Die Lösung des Fabrikproblems ist einfach. In der Tat brauchen Sie überhaupt keine Fabrik. Verwenden Sie einfach den Konstruktor der Klasse Soda .

    
strager 04.04.2010 15:46
quelle