Beim Entwerfen eines Systems stehe ich häufig vor dem Problem, dass eine Reihe von Modulen (Protokollierung, Datenbankzugriff usw.) von den anderen Modulen verwendet werden. Die Frage ist, wie ich diese Komponenten anderen Komponenten zur Verfügung stelle. Zwei Antworten erscheinen als mögliche Abhängigkeitsinjektion oder unter Verwendung des Factory-Musters. Beide scheinen jedoch falsch zu sein:
- Fabriken machen das Testen zum Problem und ermöglichen kein einfaches Austauschen von Implementierungen. Sie machen auch keine Abhängigkeiten sichtbar (z. B. untersuchen Sie eine Methode, ohne zu bemerken, dass sie eine Methode aufruft, die eine Methode aufruft, die eine Methode aufruft, die eine Datenbank verwendet).
- Die Dependecy-Injektion vergrößert Konstruktorargumentlisten massiv und verschmiert einige Aspekte im gesamten Code. Typischerweise sehen Konstrukteure von mehr als einer halben Klasse so aus
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Hier ist eine typische Situation, mit der ich ein Problem habe: Ich habe Ausnahmeklassen, die Fehlerbeschreibungen verwenden, die aus der Datenbank geladen wurden. Dabei wird eine Abfrage verwendet, die Parameter für die Benutzerspracheneinstellung enthält und sich im Benutzersitzungsobjekt befindet. Um eine neue Ausnahme zu erstellen, benötige ich eine Beschreibung, die eine Datenbanksitzung und die Benutzersitzung erfordert. Daher bin ich dazu verdammt, all diese Objekte über alle meine Methoden zu ziehen, nur für den Fall, dass ich eine Ausnahme auslösen muss.
Wie gehe ich ein solches Problem an?
Antworten:
Verwenden Sie die Abhängigkeitsinjektion. Wenn Ihre Konstruktorargumentlisten jedoch zu umfangreich werden, können Sie sie mithilfe eines Facade-Service umgestalten . Die Idee ist, einige der Konstruktorargumente zu gruppieren und eine neue Abstraktion einzuführen.
Beispielsweise könnten Sie einen neuen Typ einführen , der
SessionEnvironment
einDBSessionProvider
, dasUserSession
und das Geladene einkapseltDescriptions
. Um zu wissen, welche Abstraktionen am sinnvollsten sind, müssen Sie jedoch die Details Ihres Programms kennen.Eine ähnliche Frage wurde hier bereits zu SO gestellt .
quelle
Aus diesem Grund scheinen Sie DI nicht richtig zu verstehen - die Idee ist, das Objektinstanziierungsmuster innerhalb einer Fabrik zu invertieren.
Ihr spezielles Problem scheint ein allgemeineres OOP-Problem zu sein. Warum können die Objekte während der Laufzeit nicht einfach normale, nicht von Menschen lesbare Ausnahmen auslösen und haben dann etwas vor dem letzten Versuch / Fang, der diese Ausnahme abfängt, und verwenden an diesem Punkt die Sitzungsinformationen, um eine neue, schönere Ausnahme auszulösen ?
Ein anderer Ansatz wäre eine Exception-Factory, die über ihre Konstruktoren an die Objekte übergeben wird. Anstatt eine neue Ausnahme auszulösen, kann die Klasse eine Methode der Factory auslösen (z
throw PrettyExceptionFactory.createException(data)
. B.Denken Sie daran, dass Ihre Objekte, abgesehen von Ihren Werksobjekten, niemals den
new
Operator verwenden sollten. Ausnahmen sind in der Regel der einzige Sonderfall, in Ihrem Fall jedoch möglicherweise eine Ausnahme!quelle
Builder
Muster) diktiert es. Wenn Sie Parameter an Ihren Konstruktor übergeben, weil Ihr Objekt andere Objekte instanziiert, ist dies ein klarer Fall für IoC.Sie haben die Nachteile des statischen Fabrikmusters bereits recht gut aufgelistet, aber ich stimme den Nachteilen des Abhängigkeitsinjektionsmusters nicht ganz zu:
Diese Abhängigkeitsinjektion erfordert, dass Sie Code für jede Abhängigkeit schreiben. Dies ist kein Fehler, sondern eine Funktion: Sie müssen überlegen, ob Sie diese Abhängigkeiten wirklich benötigen, und fördern so die lose Kopplung. In deinem Beispiel:
Nein, du bist nicht zum Scheitern verurteilt. Warum liegt es in der Verantwortung der Geschäftslogik, Ihre Fehlermeldungen für eine bestimmte Benutzersitzung zu lokalisieren? Was wäre, wenn Sie irgendwann in der Zukunft diesen Business-Service aus einem Stapelverarbeitungsprogramm (das keine Benutzersitzung hat ...) verwenden möchten? Oder was ist, wenn die Fehlermeldung nicht dem aktuell angemeldeten Benutzer, sondern seinem Vorgesetzten (der möglicherweise eine andere Sprache bevorzugt) angezeigt werden soll? Oder was, wenn Sie Geschäftslogik auf dem Client wiederverwenden möchten (der keinen Zugriff auf eine Datenbank hat ...)?
Das Lokalisieren von Nachrichten hängt natürlich davon ab, wer diese Nachrichten ansieht, dh, es liegt in der Verantwortung der Präsentationsschicht. Daher würde ich normale Ausnahmen vom Business Service auslösen, die eine Nachrichtenkennung enthalten, die dann im Ausnahmehandler der Präsentationsschicht in der jeweils verwendeten Nachrichtenquelle nachgeschlagen werden kann.
Auf diese Weise können Sie 3 unnötige Abhängigkeiten (UserSession, ExceptionFactory und wahrscheinlich Beschreibungen) entfernen, wodurch Ihr Code einfacher und vielseitiger wird.
Im Allgemeinen würde ich statische Fabriken nur für Dinge verwenden, auf die Sie allgegenwärtigen Zugriff benötigen, und die garantiert in allen Umgebungen verfügbar sind, in denen wir den Code jemals ausführen möchten (z. B. Protokollierung). Für alles andere würde ich normale alte Abhängigkeitsinjektion verwenden.
quelle
Verwenden Sie die Abhängigkeitsinjektion. Die Verwendung statischer Fabriken ist eine Beschäftigung mit dem
Service Locator
Antimuster. Die wegweisenden Arbeiten von Martin Fowler finden Sie hier - http://martinfowler.com/articles/injection.htmlWenn Ihre Konstruktorargumente zu groß werden und Sie keinesfalls einen DI-Container verwenden, schreiben Sie Ihre eigenen Factorys für die Instanziierung und lassen Sie diese entweder per XML oder durch Bindung einer Implementierung an eine Schnittstelle konfigurieren.
quelle
Ich würde auch mit Abhängigkeitsinjektion gehen. Denken Sie daran, dass DI nicht nur über Konstruktoren, sondern auch über Eigenschaftssetzer erfolgt. Zum Beispiel könnte der Logger als eine Eigenschaft injiziert werden.
Möglicherweise möchten Sie auch einen IoC-Container verwenden, der Sie möglicherweise entlastet, z. B. indem Sie die Konstruktorparameter auf die zur Laufzeit von Ihrer Domänenlogik benötigten Werte beschränken (wobei der Konstruktor auf eine Weise beibehalten wird, die die Absicht von enthüllt) die Klassen- und die realen Domänenabhängigkeiten) und möglicherweise andere Hilfsklassen über Eigenschaften einschleusen.
Ein weiterer Schritt, den Sie vielleicht machen möchten, ist die aspektorientierte Programmierung, die in vielen wichtigen Frameworks implementiert ist. Auf diese Weise können Sie den Konstruktor der Klasse abfangen (oder zur Verwendung der AspectJ-Terminologie "raten") und die relevanten Eigenschaften einfügen, möglicherweise mit einem speziellen Attribut versehen.
quelle
Da stimme ich nicht ganz zu Zumindest nicht im Allgemeinen.
Einfache fabrik:
Einfache Injektion:
Beide Schnipsel haben den gleichen Zweck, sie stellen eine Verbindung zwischen
IFoo
und herFoo
. Alles andere ist nur Syntax.Das Ändern von
Foo
zuADifferentFoo
erfordert in beiden Codebeispielen genau so viel Aufwand.Ich habe gehört, dass die Leute argumentieren, dass die Abhängigkeitsinjektion die Verwendung unterschiedlicher Bindungen zulässt, aber dasselbe Argument für die Herstellung unterschiedlicher Fabriken vorgebracht werden kann. Die Auswahl der richtigen Bindung ist genauso komplex wie die Auswahl der richtigen Fabrik.
Mit Factory-Methoden können Sie z. B.
Foo
an einigen Orten undADifferentFoo
an anderen Orten verwenden. Einige nennen das gut (nützlich, wenn Sie es brauchen), andere nennen es schlecht (Sie könnten einen halbherzigen Job machen, wenn Sie alles ersetzen).Es ist jedoch nicht
IFoo
allzu schwer, diese Mehrdeutigkeit zu vermeiden, wenn Sie sich an eine einzelne Methode halten, die zurückgibt, sodass Sie immer eine einzige Quelle haben. Wenn Sie sich nicht in den Fuß schießen möchten, halten Sie entweder keine geladene Waffe oder zielen Sie nicht auf Ihren Fuß.Aus diesem Grund ziehen es einige Leute vor, Abhängigkeiten im Konstruktor explizit abzurufen:
Ich habe Argumente pro (kein Konstruktor aufgebläht) gehört, ich habe Argumente con (die Verwendung des Konstruktors ermöglicht mehr DI-Automatisierung) gehört.
Persönlich habe ich, während ich unserem Vorgesetzten nachgegeben habe, der Konstruktorargumente verwenden möchte, ein Problem mit der Dropdown-Liste in VS (oben rechts, um die Methoden der aktuellen Klasse zu durchsuchen) festgestellt, bei dem die Methodennamen in einem Fall außer Sichtweite geraten der Methodensignaturen ist länger als mein Bildschirm (=> der aufgeblähte Konstruktor).
Aus technischer Sicht ist mir das egal. Die Eingabe beider Optionen erfordert ungefähr den gleichen Aufwand. Und da Sie DI verwenden, rufen Sie einen Konstruktor normalerweise sowieso nicht manuell auf. Aber der Fehler in der Visual Studio-Benutzeroberfläche lässt mich das Konstruktorargument nicht aufblähen.
Nebenbei bemerkt, die Abhängigkeitsinjektion und die Fabriken schließen sich nicht gegenseitig aus . Es gab Fälle, in denen ich anstelle einer Abhängigkeit eine Factory eingefügt habe, die Abhängigkeiten generiert (NInject ermöglicht Ihnen zum Glück die Verwendung,
Func<IFoo>
sodass Sie keine tatsächliche Factory-Klasse erstellen müssen).Die Anwendungsfälle hierfür sind selten, aber vorhanden.
quelle
In diesem Beispiel wird zur Laufzeit eine Factory-Klasse verwendet, um basierend auf der HTTP-Anforderungsmethode zu bestimmen, welche Art von eingehendem HTTP-Anforderungsobjekt instanziiert werden soll. Die Fabrik selbst wird mit einer Instanz eines Abhängigkeitsinjektionsbehälters injiziert. Auf diese Weise kann die Factory die Laufzeit ermitteln und die Abhängigkeiten vom Abhängigkeitsinjektionscontainer verarbeiten lassen. Jedes eingehende HTTP-Anforderungsobjekt weist mindestens vier Abhängigkeiten auf (Superglobale und andere Objekte).
Der Client-Code für ein MVC-Typ-Setup in einem zentralen
index.php
System sieht möglicherweise wie folgt aus (Überprüfungen werden weggelassen).Alternativ können Sie die statische Natur (und das Schlüsselwort) der Factory entfernen und einem Abhängigkeitsinjektor erlauben, das Ganze zu verwalten (daher der auskommentierte Konstruktor). Sie müssen jedoch einige (nicht die Konstanten) der Klassenmitgliedsreferenzen (
self
) in Instanzmitglieder ($this
) ändern .quelle