Verwendung von Dependency Injection in Verbindung mit dem Factory-Muster

11

Stellen Sie sich ein Modul vor, das für das Parsen von Dateien eines bestimmten Typs verantwortlich ist. Ich denke darüber nach, das Strategiemuster zu verwenden, um dieses Problem anzugehen, wie ich hier bereits erklärt habe . Bitte lesen Sie den verlinkten Beitrag, bevor Sie mit dieser Frage fortfahren.

Betrachten Sie Klasse B, die den Inhalt der Datei product.xml benötigt. Diese Klasse muss den entsprechenden konkreten Implementierer der Parser-Schnittstelle instanziieren, um die XML-Datei zu analysieren. Ich kann die Instanziierung des geeigneten konkreten Implementierers an eine Fabrik delegieren, so dass Klasse B eine Fabrik hat. Klasse B "hängt" dann jedoch von einer Fabrik ab, um den konkreten Implementierer zu instanziieren. Dies bedeutet, dass der Konstruktor oder eine Setter-Methode in Klasse B an die Factory übergeben werden muss.

Daher sind Factory und Klasse B, die eine Datei analysieren müssen, eng miteinander verbunden. Ich verstehe, dass ich mich in Bezug auf alles, was ich bisher erklärt habe, möglicherweise völlig irre. Ich möchte wissen, ob ich die Abhängigkeitsinjektion in einem Szenario verwenden kann, in dem die zu injizierende Abhängigkeit eine Factory ist, und wie dies richtig implementiert werden kann, damit ich Bereiche wie das Verspotten der Factory in meinen Komponententests nutzen kann.

CKing
quelle

Antworten:

8

Der richtige Weg, dies zu tun, besteht darin, von einer Schnittstelle abhängig zu sein und dann eine Implementierung dieser Schnittstelle in Klasse B einzufügen.

Eine Schnittstelle ist fast das Dünnste, worauf Sie sich verlassen können - ich vergleiche sie mit dem Versuch, eine Rauchwolke zu schnappen. Code muss an etwas gekoppelt werden, sonst wird er nichts tun, aber die Kopplung an eine Schnittstelle ist so entkoppelt wie möglich, aber Schnittstellen können alle Funktionen bieten, die Sie sich wünschen können.

Lassen Sie den Konstruktor der Klasse B eine Schnittstelle zu der Klasse erstellen, die er benötigt, und die Fabrik diese Klasse als Implementierer der Schnittstelle erstellen. Verlassen Sie sich nicht auf die Fabrik, sondern auf die Schnittstelle und lassen Sie die Fabrik eine Implementierung dieser Fabrik bereitstellen.

Also ja, Sie werden die Abhängigkeitsinjektion verwenden, aber daran ist nichts auszusetzen. Die Abhängigkeitsinjektion - insbesondere die einfache Konstruktorinjektion - sollte die "normale" Vorgehensweise sein. Verschieben Sie neue Dinge einfach so weit mainwie möglich in Ihrer App (und so nah wie möglich an der ersten Zeile ) und verbergen Sie den neuen Anruf in einer Klasse, die speziell für das Erstellen von Dingen entwickelt wurde.

Fazit: Zögern Sie nicht, Abhängigkeiten einzufügen. Das sollte die normale Art sein, Dinge zu tun.

Nick Hodges
quelle
Verschiebt die Konstruktorinjektion neue Dinge so lange wie möglich?
JeffO
Es war schlecht ausgedrückt - ich habe es behoben, um zu sagen "so weit wie möglich in Ihrer App zurück".
Nick Hodges
Ja. Ich dachte daran, von der Parser-Oberfläche anstatt von einer Fabrik abhängig zu sein. Wenn nun eine andere Klasse Klasse B verwendet, muss dies von der Schnittstelle abhängen, von der Klasse B abhängt. Dies wird bis zur höchsten Klasse fortgesetzt. Diese Klasse der obersten Ebene muss schließlich eine Factory verwenden, um die entsprechende konkrete Instanz des Parsers zu instanziieren. Sollte die Klasse der obersten Ebene von einer Fabrik abhängen oder sollte sie die Fabrik direkt innerhalb ihrer Methoden verwenden?
CKing
1
Gut gesagt: Zögern Sie nicht, Abhängigkeiten zu injizieren
Signcodeindie
9

Ich denke, Ihre Prämisse ist hier ein wenig verwirrt. Sie sprechen vom Injizieren einer Fabrik, aber das Fabrikmuster ist ein Kreationsmuster, dessen Zweck darin bestand, eine Teilmenge dessen zu tun, was ein Abhängigkeitsinjektionsframework tut, wenn DI-Frameworks nicht vorherrschend waren nützlich aus diesem Grund. Wenn Sie jedoch über ein DI-Framework verfügen, benötigen Sie keine Factory mehr, da das DI-Framework den Zweck erfüllen kann, den die Factory erfüllt hätte.

Lassen Sie mich dennoch ein wenig über die Abhängigkeitsinjektion und deren allgemeine Verwendung erklären.

Es gibt verschiedene Möglichkeiten, eine Abhängigkeitsinjektion durchzuführen. Am häufigsten werden jedoch Konstruktorinjektion, Eigenschaftsinjektion und direkter DIContainer verwendet. Ich werde über die Konstruktorinjektion sprechen, da die Eigenschaftsinjektion die meiste Zeit der falsche Ansatz ist (manchmal der richtige Ansatz) und der DIContainer-Zugriff nicht vorzuziehen ist, es sei denn, Sie können absolut keinen der anderen Ansätze ausführen.

Bei der Konstruktorinjektion haben Sie die Schnittstelle für eine Abhängigkeit und einen DIContainer (oder eine Factory), die die konkrete Implementierung für diese Abhängigkeit kennt, und wo immer Sie ein Objekt benötigen, das von dieser Schnittstelle abhängt, übergeben Sie die Implementierung zur Konstruktionszeit von der Factory an es.

dh

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

Viele DI-Frameworks können dies erheblich vereinfachen, indem Ihr DIContainer den Konstruktor von UserRepository auf Schnittstellen überprüft, für die er konkrete Implementierungen kennt, und diese automatisch an Sie weitergibt. Diese Technik wird häufig als Inversion of Control bezeichnet, obwohl DI und IoC Begriffe sind, die häufig ausgetauscht werden und vage (wenn überhaupt) Unterschiede aufweisen.

Wenn Sie sich jetzt fragen, wie der übergreifende Code auf den DIContainer zugreift, können Sie entweder eine statische Klasse für den Zugriff darauf haben oder besser geeignet sein, dass die meisten DI-Frameworks es Ihnen ermöglichen, einen DIContainer neu zu erstellen, bei dem er sich tatsächlich nur so verhält Ein Wrapper für ein internes Singleton-Wörterbuch für die Typen, von denen bekannt ist, dass sie für bestimmte Schnittstellen konkret sind.

Das heißt, Sie können den DIContainer an einer beliebigen Stelle im Code neu erstellen und effektiv denselben DIContainer erhalten, den Sie bereits konfiguriert haben, um Ihre Beziehungen zwischen Schnittstelle und Beton zu kennen. Das übliche Mittel, um den DIContainer vor Teilen des Codes zu verbergen, die nicht direkt mit ihm interagieren sollten, besteht darin, einfach sicherzustellen, dass nur die erforderlichen Projekte einen Verweis auf das DI-Framework haben.

Jimmy Hoffa
quelle
Ich stimme dem ersten Absatz nicht vollständig zu. Es gibt immer noch Szenarien, in denen Sie von einer Factory (Implementierung der Schnittstelle) abhängig sind, die vom DI-Container injiziert wird.
Piotr Perak
Ich denke, Sie haben den Punkt verpasst, da das OP nicht über DI-Frameworks oder DI-Container sprach.
Doc Brown
@ Jimmy Hoffa Während Sie einige sehr interessante Punkte angesprochen haben, sind mir IoC-Container wie Spring und das Konzept der Abhängigkeitsinjektion bekannt. Mein Anliegen war ein Szenario, in dem Sie kein IoC-Framework verwenden. In diesem Fall müssen Sie eine Klasse schreiben, die Ihre Abhängigkeiten für Sie instanziiert. Wenn Klasse A von Schnittstelle B abhängt und Klasse C Klasse A verwendet, hängt Klasse C von Schnittstelle B ab. Jemand muss Klasse C eine Klasse B ce geben. Sollte Klasse C dann von cla
CKing
Wenn Klasse A von Schnittstelle B abhängt und Klasse C Klasse A verwendet, hängt Klasse C von Schnittstelle B ab. Jemand muss Klasse C eine Schnittstelle B geben. Sollte Klasse C dann von Schnittstelle B abhängen oder sollte es von der Klasse abhängen, die die instanziiert hat Abhängigkeiten dh die Fabrik. Sie scheinen diese Frage in Ihrer Antwort beantwortet zu haben, aber mit einer Informationsüberflutung :)
CKing
@bot Wie ich bereits im Vorwort sagte, können Sie Fabriken größtenteils als Teilmenge der Funktionalität eines DI-Containers behandeln. Dies gilt nicht für alle Szenarien, wie Doc Brown erwähnt hat. Schauen Sie sich jedoch mein Codebeispiel an und ersetzen Sie es DIContainerdurch DbConnectionFactoryund das Konzept Es steht immer noch fest, dass Sie die konkrete Implementierung von Ihrem DI / Factory / etc abrufen und sie den Verbrauchern des Typs zum Zeitpunkt ihrer Erstellung übergeben würden.
Jimmy Hoffa
5

Sie können eine Factory über die Abhängigkeitsinjektion übergeben, genau wie Sie alles andere übergeben. Lassen Sie sich nicht von der Rekursivität der Situation verwirren. Ich weiß nicht, was ich sonst noch zur Implementierung sagen soll - Sie wissen bereits, wie man Abhängigkeitsinjektionen durchführt.

Ich benutze DI, um Fabriken ziemlich regelmäßig zu injizieren.

psr
quelle
1
Wenn eine Klasse von einer Fabrik abhängt, müssen Sie die Fabrik verspotten, wenn Sie diese Klasse testen. Wie machst du das? Lassen Sie die Klasse von einer Factory-Schnittstelle abhängen?
CKing
4

Es ist nichts Falsches daran, Fabriken zu injizieren. Dies ist die Standardmethode, wenn Sie beim Erstellen des übergeordneten Objekts nicht entscheiden können, welche Art von Abhängigkeit Sie benötigen. Ich denke, das Beispiel wird es am besten erklären. Ich werde c # verwenden, weil ich Java nicht kenne.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Piotr Perak
quelle