Wann sollten wir die Abhängigkeitsinjektion (C #) verwenden? [Geschlossen]

8

Ich möchte sichergehen, dass ich das Konzept der Abhängigkeitsinjektion (DI) verstehe. Nun, ich verstehe das Konzept tatsächlich, DI ist nicht kompliziert: Sie erstellen eine Schnittstelle und übergeben dann die Implementierung meiner Schnittstelle an die Klasse, die sie verwendet. Die übliche Methode zum Übergeben ist der Konstruktor. Sie können ihn jedoch auch per Setter oder einer anderen Methode übergeben.

Ich bin mir nicht sicher, wann ich DI verwenden soll.

Verwendung 1: Natürlich erscheint die Verwendung von DI für den Fall, dass Ihre Schnittstelle mehrfach implementiert ist, logisch. Sie haben ein Repository für Ihren SQL Server und ein anderes für Ihre Oracle-Datenbank. Beide teilen sich die gleiche Schnittstelle und Sie "injizieren" (dies ist der verwendete Begriff) diejenige, die Sie zur Laufzeit möchten. Dies ist sogar nicht DI, dies ist hier die grundlegende OO-Programmierung.

Verwendung 2: Wenn Sie eine Geschäftsschicht mit vielen Diensten mit all ihren spezifischen Methoden haben, scheint es die gute Praxis zu sein, für jeden Dienst eine Schnittstelle zu erstellen und die Implementierung auch dann einzufügen, wenn diese eindeutig ist. Weil dies besser für die Wartung ist. Dies ist diese zweite Verwendung, die ich nicht verstehe.

Ich habe ungefähr 50 Businessklassen. Nichts ist zwischen ihnen gemeinsam. Einige sind Repositorys, die Daten in 3 verschiedenen Datenbanken abrufen oder speichern. Einige lesen oder schreiben Dateien. Einige machen reine Geschäftsaktionen. Es gibt auch spezielle Validatoren und Helfer. Die Herausforderung ist die Speicherverwaltung, da einige Klassen von verschiedenen Standorten aus instanziiert werden. Ein Validator kann mehrere Repositorys und andere Validatoren aufrufen, die dieselben Repositorys erneut aufrufen können.

Beispiel: Geschäftsschicht

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
        using (var itemService = new ItemService())
        {
            var item = itemService.Read(itemCode);
            return Read(item, site);
        }
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
        using (var itemService = new ItemService())
        using (var siteService = new SiteService())
        {
            var item = itemService.Read(itemCode);
            var site = siteService.Read(itemCode, siteCode);
            return Read(item, site);
        }
    }
}

Regler

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
        using (var service = new ItemSiteService())
        {
            var itemSite = service.Read(itemCode, siteCode);
            return Ok(itemSite);
        }
    }
}

Dieses Beispiel ist sehr einfach, aber Sie sehen, wie ich leicht zwei Instanzen von itemService erstellen kann, um eine itemSite zu erhalten. Dann kommt auch jeder Dienst mit seinem DB-Kontext. Dieser Aufruf erstellt also 3 DbContext. 3 Verbindungen.

Meine erste Idee war, Singleton zu erstellen, um all diesen Code wie unten umzuschreiben. Der Code ist besser lesbar und am wichtigsten ist, dass das Singleton-System nur eine Instanz jedes verwendeten Dienstes erstellt und beim ersten Aufruf erstellt. Perfekt, außer dass ich immer noch einen anderen Kontext habe, aber ich kann das gleiche System für meine Kontexte verwenden. So fertig.

Geschäftsschicht

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
            var item = ItemService.Instance.Read(itemCode);
            return Read(item, site);
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
            var item = ItemService.Instance.Read(itemCode);
            var site = SiteService.Instance.Read(itemCode, siteCode);
            return Read(item, site);       
    }
}

Regler

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
            var itemSite = service.Instance.Read(itemCode, siteCode);
            return Ok(itemSite);
    }
}

Einige Leute sagen mir nach guter Praxis, ich sollte DI mit einer einzelnen Instanz verwenden, und Singleton ist eine schlechte Praxis. Ich sollte für jede Business Class eine Schnittstelle erstellen und diese mithilfe eines DI-Containers instanziieren. "Ja wirklich?" Dieser DI vereinfacht meinen Code. Kaum zu glauben.

Bastien Vandamme
quelle
4
Bei DI ging es nie um Lesbarkeit.
Robert Harvey

Antworten:

14

Der "beliebteste" Anwendungsfall für DI (abgesehen von der bereits beschriebenen "Strategie" -Musterverwendung) ist wahrscheinlich das Testen von Einheiten.

Selbst wenn Sie glauben, dass es nur eine "echte" Implementierung für eine injizierte Schnittstelle gibt, gibt es für Unit-Tests normalerweise eine zweite: eine "Schein" -Implementierung mit dem einzigen Zweck, einen isolierten Test zu ermöglichen. Dies gibt Ihnen den Vorteil, dass Sie sich nicht mit der Komplexität, den möglichen Fehlern und möglicherweise den Auswirkungen auf die Leistung der "echten" Komponente auseinandersetzen müssen.

Nein, DI dient nicht zur Verbesserung der Lesbarkeit, sondern zur Erhöhung der Testbarkeit (- natürlich nicht ausschließlich).

Dies ist kein Selbstzweck. Wenn Ihre Klasse ItemServicesehr einfach ist und keinen externen Netzwerk- oder Datenbankzugriff ermöglicht, so dass das Schreiben von Komponententests für so etwas nicht behindert wird, SiteServicekann es sich lohnen, diese isoliert zu testen, sodass DI dies nicht ist notwendig. Wenn ItemServiceSie jedoch über das Netzwerk auf andere Sites zugreifen, möchten Sie wahrscheinlich einen SiteServicevon diesem entkoppelten Unit-Test durchführen. Dies kann erreicht werden, indem Sie das "echte" ItemServicedurch ein a ersetzen MockItemService, das einige fest codierte gefälschte Elemente liefert.

Lassen Sie mich auf eine andere Sache hinweisen: In Ihren Beispielen könnte man argumentieren, dass DI hier nicht zum Testen der Kerngeschäftslogik benötigt wird - die Beispiele zeigen immer zwei Varianten der ReadMethoden, eine mit der tatsächlichen Geschäftslogik (die es sein kann) Gerät ohne DI getestet) und eines, das nur der "Kleber" -Code zum Anschließen der ItemServicean die vorherige Logik ist. Im gezeigten Fall ist dies in der Tat ein gültiges Argument gegen DI. Wenn DI vermieden werden kann, ohne die Testbarkeit auf diese Weise zu beeinträchtigen, fahren Sie fort. Aber nicht jeder reale Code ist so einfach, und oft ist DI die einfachste Lösung, um eine "ausreichende" Testbarkeit der Einheiten zu erreichen.

Doc Brown
quelle
1
Ich verstehe es wirklich, aber was ist, wenn ich keine Unit-Tests mache? Ich mache Integrationstests mit einer gefälschten Datenbank. Änderungen werden zu oft durchgeführt, um Komponententests aufrechtzuerhalten, und unsere Produktionsdaten ändern sich ständig. Das Testen mit einer Kopie realer Daten ist daher die einzige Möglichkeit, die wir ernsthaft testen können. Anstelle von Unit Testing schützen wir alle unsere Methoden mit Codeverträgen. Ich weiß, dass dies nicht dasselbe ist, und ich kenne Unit-Tests von Vorteil, aber ich habe nicht die Zeit, Unit-Tests Priorität einzuräumen. Das ist eine Tatsache, keine Wahl.
Bastien Vandamme
1
@ BastienVandamme: Es gibt einen großen Abstand zwischen "überhaupt kein Unit-Test" und "Wir testen alles". Und ich habe noch nie ein großes Projekt gesehen, das zumindest für einige Teile keine Vorteile bei der Verwendung von Komponententests finden konnte . Identifizieren Sie diese Teile und prüfen Sie, ob DI erforderlich ist, damit sie vom Gerät getestet werden können.
Doc Brown
4

Wenn Sie die Abhängigkeitsinjektion nicht verwenden, können Sie dauerhafte Verbindungen zu anderen Objekten herstellen. Verbindungen, die Sie verstecken können, wo sie Menschen überraschen. Verbindungen, die sie nur ändern können, indem Sie das, was Sie erstellen, neu schreiben.

Stattdessen können Sie die Abhängigkeitsinjektion (oder die Referenzübergabe, wenn Sie wie ich altmodisch sind) verwenden, um die Anforderungen eines Objekts explizit zu machen, ohne es zu zwingen, zu definieren, wie seine Anforderungen erfüllt werden müssen.

Dies zwingt Sie dazu, viele Parameter zu akzeptieren. Sogar solche mit offensichtlichen Standardeinstellungen. In C # haben Sie glückliche Soden benannte und optionale Argumente . Das heißt, Sie haben Standardargumente. Wenn es Ihnen nichts ausmacht, statisch an Ihre Standardeinstellungen gebunden zu sein, können Sie DI zulassen, ohne von Optionen überfordert zu sein, auch wenn Sie diese nicht verwenden. Dies folgt der Konvention über die Konfiguration .

Testen ist keine gute Rechtfertigung für DI. In dem Moment, in dem Sie glauben, dass jemand Ihnen ein Wiz-Bang-Mocking-Framework verkauft, das Reflexion oder eine andere Magie verwendet, um Sie davon zu überzeugen, dass Sie zu Ihrer vorherigen Arbeitsweise zurückkehren und den Rest mit Magie erledigen können.

Bei richtiger Anwendung kann das Testen ein guter Weg sein, um zu zeigen, ob ein Design isoliert ist. Aber darum geht es nicht. Es hindert Verkäufer nicht daran zu beweisen, dass mit genügend Magie alles isoliert ist. Halte die Magie auf ein Minimum.

Der Sinn dieser Isolation besteht darin, Veränderungen zu verwalten. Es ist schön, wenn eine Änderung an einem Ort vorgenommen werden kann. Es ist nicht schön, Datei für Datei durchgehen zu müssen, in der Hoffnung, dass der Wahnsinn endet.

Bringen Sie mich in einen Laden, der sich weigert, Unit-Tests durchzuführen, und ich mache trotzdem DI. Ich mache es, weil ich dadurch trennen kann, was benötigt wird und wie es gemacht wird. Testen oder kein Testen Ich möchte diese Isolation.

candied_orange
quelle
2
Können Sie erläutern, warum das Testen keine gute Rechtfertigung für DI ist? Sie scheinen es mit magischen Reflexionsrahmen zu verbinden - und ich verstehe, dass Sie diese nicht mögen -, aber Sie machen keinen Kontrapunkt zur Idee selbst.
Jacob Raihle
1
Das spöttische Argument scheint ein rutschiger Hang zu sein. Ich verwende keine spöttischen Frameworks, aber ich verstehe die Vorteile von DI.
Robert Harvey
2
Es ist nur ein warnender Schwanz. Wenn die Leute denken, dass das Aktivieren automatisierter Tests der einzige Grund für DI ist, verpassen sie nicht nur das Verständnis für die Vorteile der Designflexibilität, sondern sind auch anfällig für Verkaufsgespräche für Produkte, die ohne Arbeit Vorteile versprechen. DI ist Arbeit. Es gibt kein kostenloses Mittagessen.
candied_orange
3
Anders ausgedrückt ist die Entkopplung der wahre Grund für DI. Testen ist einfach einer der häufigsten Bereiche, in denen die Vorteile der Entkopplung offensichtlich sind.
StackOverthrow
2
Hey, kein Witz. Wenn DI Ihnen nicht hilft, Ihren Code zu pflegen, verwenden Sie ihn nicht.
candied_orange
2

Die Hubschrauberansicht von DI ist einfach die Möglichkeit, eine Implementierung gegen eine Schnittstelle auszutauschen . Während dies natürlich ein Segen für das Testen ist, gibt es andere potenzielle Vorteile:

Versionierung von Implementierungen eines Objekts

Wenn Ihre Methoden Schnittstellenparameter in den mittleren Ebenen akzeptieren, können Sie beliebige Implementierungen in der obersten Ebene übergeben, wodurch sich die Menge an Code verringert, die zum Austauschen von Implementierungen geschrieben werden muss. Zugegeben, dies ist ohnehin ein Vorteil von Schnittstellen, aber wenn der Code unter Berücksichtigung von DI geschrieben wurde, werden Sie diesen Vorteil sofort erreichen.

Reduzieren der Anzahl der Objekte, die Ebenen durchlaufen müssen

Während dies hauptsächlich für DI-Frameworks gilt , ist es möglich, den Kernel (oder was auch immer) abzufragen, um Objekt B im laufenden Betrieb zu generieren , anstatt es durch die Ebenen zu leiten, wenn Objekt A Instanzen von Objekt B benötigt . Dies reduziert die Menge an Code, die geschrieben und getestet werden muss. Außerdem werden Ebenen, die sich nicht um Objekt B kümmern, sauber gehalten.

Robbie Dee
quelle
2

Es ist nicht erforderlich, Schnittstellen zu verwenden, um DI zu verwenden. Der Hauptzweck von DI besteht darin, die Konstruktion und Verwendung von Objekten zu trennen.

Die Verwendung von Singletons ist in den meisten Fällen zu Recht verpönt. Einer der Gründe ist, dass es sehr schwierig wird, sich einen Überblick über die Abhängigkeiten einer Klasse zu verschaffen.

In Ihrem Beispiel könnte der ItemSiteController einfach einen ItemSiteService als Konstruktorargument verwenden. Auf diese Weise können Sie Kosten für das Erstellen von Objekten vermeiden, aber die Inflexibilität eines Singletons vermeiden. Das Gleiche gilt für ItemSiteService. Wenn ein ItemService und ein SiteService benötigt werden, fügen Sie diese in den Konstruktor ein.

Der Vorteil ist am größten, wenn alle Objekte die Abhängigkeitsinjektion verwenden. Auf diese Weise können Sie die Konstruktion in einem dedizierten Modul zentralisieren oder an einen DI-Container delegieren.

Eine Abhängigkeitshierarchie könnte ungefähr so ​​aussehen:

public interface IStorage
{
}

public class DbStorage : IStorage
{
    public DbStorage(string connectionString){}
}

public class FileStorage : IStorage
{
    public FileStorage(FileInfo file){}
}

public class MemoryStorage : IStorage
{
}

public class CachingStorage : IStorage
{
    public CachingStorage(IStorage storage) { }
}

public class MyService
{
    public MyService(IStorage storage){}
}

public class Controller
{
    public Controller(MyService service){}
}

Beachten Sie, dass es nur eine Klasse ohne Konstruktorparameter und nur eine Schnittstelle gibt. Bei der Konfiguration des DI-Containers können Sie entscheiden, welcher Speicher verwendet werden soll oder ob Caching verwendet werden soll usw. Das Testen ist einfacher, da Sie entscheiden können, welche Datenbank verwendet werden soll oder welche andere Art von Speicher verwendet werden soll. Sie können den DI-Container auch so konfigurieren, dass Objekte bei Bedarf im Kontext des Containerobjekts als Singletons behandelt werden.

JonasH
quelle
"In Ihrem Beispiel könnte der ItemSiteController einfach einen ItemSiteService als Konstruktorargument verwenden." Natürlich kann aber ein Controller 1 bis 10 Dienste nutzen. Dann kann ich irgendwann einen neuen Dienst hinzufügen, der mich zwingt, die Konstruktorsignatur zu ändern. Wie können Sie sagen, dass dies für die Wartung besser ist? Und nein, das kostet mich wahrscheinlich mehrere Male, um das Objekt zu erstellen, da ich den Singleton-Mechanismus nicht mehr habe. Also muss ich es an jeden Controller weitergeben, der es verwendet. Wie kann ich sicherstellen, dass Entwickler es nicht immer neu erstellen? Welches System kann sicherstellen, dass ich es nur einmal erstelle?
Bastien Vandamme
Ich denke, wie die meisten Leute mischen Sie das DI-Muster mit der Container-Funktion. Ihr Beispiel ist meine Verwendung 1. Ich fordere diese nicht heraus. Wenn Sie eine Schnittstelle mehrfach implementiert haben, müssen Sie das verwenden, was Sie DI nennen. Ich nenne das OOP. Wie auch immer. Ich fordere die Verwendung 2 heraus, bei der die meisten Leute sie anscheinend zum Testen und zur Wartung verwenden. Ich verstehe, aber dies löst meine Einzelinstanzanforderung nicht. Hoffentlich scheint Container eine Lösung anzubieten. Container… nicht nur DI. Ich glaube, ich brauche DI mit einer einzelnen Instanzfunktion und kann diese in Containern (Bibliotheken von Drittanbietern) finden.
Bastien Vandamme
@ bastien-vandamme Wenn Sie neue Dienste hinzufügen müssen, sollten Sie diese dem Konstruktor hinzufügen, dies sollte kein Problem sein. Wenn Sie viele Dienste einfügen müssen, kann dies auf ein Problem mit der Architektur hinweisen, nicht auf die Idee, die Abhängigkeiten einzufügen. Sie stellen sicher, dass dieselbe Instanz von anderen Entwicklern wiederverwendet wird, indem Sie Ihre Kollegen gut entwerfen und schulen. Im Allgemeinen sollte es jedoch möglich sein, mehrere Instanzen einer Klasse zu haben. Als Beispiel könnte ich mehrere Datenbankinstanzen haben und für jede einen Dienst haben.
JonasH
Euh nein. Ich habe eine Datenbank mit 50 Tabellen, ich benötige 50 Repositorys, 50 Dienste. Ich kann mit generischen Repositorys arbeiten, aber meine Datenbank ist weit davon entfernt, normalisiert und sauber zu sein. Daher müssen meine Repositorys oder Dienste aufgrund des Verlaufs einen bestimmten Code haben. Also kann ich nicht alles generisch machen. Jeder Service hat spezifische Geschäftsregeln, die ich separat pflegen muss.
Bastien Vandamme
2

Sie isolieren externe Systeme.

Verwendung 1: Natürlich erscheint die Verwendung von DI für den Fall, dass Ihre Schnittstelle mehrfach implementiert ist, logisch. Sie haben ein Repository für Ihren SQL Server und ein anderes für Ihre Oracle-Datenbank. Beide teilen sich die gleiche Schnittstelle und Sie "injizieren" (dies ist der verwendete Begriff) diejenige, die Sie zur Laufzeit möchten. Dies ist sogar nicht DI, dies ist hier die grundlegende OO-Programmierung.


Ja, hier DI verwenden. Wenn es sich um ein Netzwerk, eine Datenbank, ein Dateisystem, einen anderen Prozess, Benutzereingaben usw. handelt, möchten Sie es isolieren.

Die Verwendung von DI erleichtert das Testen, da Sie diese externen Systeme leicht verspotten können. Nein, ich sage nicht, dass dies der erste Schritt zum Testen von Einheiten ist. Sie können auch nicht testen, ohne dies zu tun.

Selbst wenn Sie nur eine Datenbank hätten, würde Ihnen die Verwendung von DI an dem Tag helfen, an dem Sie migrieren möchten. Also ja, DI.

Verwendung 2: Wenn Sie eine Geschäftsschicht mit vielen Diensten mit all ihren spezifischen Methoden haben, scheint es die gute Praxis zu sein, für jeden Dienst eine Schnittstelle zu erstellen und die Implementierung auch dann einzufügen, wenn diese eindeutig ist. Weil dies besser für die Wartung ist. Dies ist diese zweite Verwendung, die ich nicht verstehe.

Sicher, DI kann Ihnen helfen. Ich würde über die Container diskutieren.

Bemerkenswert ist vielleicht, dass die Abhängigkeitsinjektion mit konkreten Typen immer noch eine Abhängigkeitsinjektion ist. Wichtig ist, dass Sie benutzerdefinierte Instanzen erstellen können. Es muss keine Schnittstelleninjektion sein (obwohl die Schnittstelleninjektion vielseitiger ist, bedeutet dies nicht, dass Sie sie überall verwenden sollten).

Die Idee, eine explizite Schnittstelle für jede Klasse zu erstellen, muss sterben. In der Tat, wenn Sie nur eine Implementierung einer Schnittstelle haben würden ... YAGNI . Das Hinzufügen einer Schnittstelle ist relativ kostengünstig und kann bei Bedarf durchgeführt werden. In der Tat würde ich vorschlagen, zu warten, bis Sie zwei oder drei Kandidatenimplementierungen haben, damit Sie eine bessere Vorstellung davon haben, welche Dinge unter ihnen gemeinsam sind.

Die Kehrseite davon ist jedoch, dass Sie Schnittstellen erstellen können, die den Anforderungen des Client-Codes besser entsprechen. Wenn der Client-Code nur wenige Mitglieder einer Klasse benötigt, können Sie dafür eine Schnittstelle haben. Dies führt zu einer besseren Trennung der Schnittstellen .


Behälter?

Sie wissen, dass Sie sie nicht brauchen.

Lassen Sie es uns auf Kompromisse bringen. Es gibt Fälle, in denen sie es nicht wert sind. Ihre Klasse muss die Abhängigkeiten übernehmen, die sie vom Konstruktor benötigt. Und das könnte gut genug sein.

Ich bin wirklich kein Fan von Annotationsattributen für "Setter Injection", geschweige denn von Attributen von Drittanbietern. Ich verstehe, dass dies möglicherweise für Implementierungen erforderlich ist, die außerhalb Ihrer Kontrolle liegen. Wenn Sie sich jedoch entscheiden, die Bibliothek zu ändern, müssen sich diese ändern.

Schließlich werden Sie Routinen erstellen, um diese Objekte zu erstellen, denn um sie zu erstellen, müssen Sie zuerst diese anderen erstellen, und für diese benötigen Sie weitere ...

Nun, wenn das passiert, möchten Sie all diese Logik an einem einzigen Ort platzieren und wiederverwenden. Sie möchten eine einzige Quelle der Wahrheit darüber, wie Sie Ihr Objekt erstellen. Und du bekommst es, wenn du dich nicht wiederholst . Das vereinfacht Ihren Code. Macht Sinn, oder?

Wo setzen Sie diese Logik ein? Der erste Instinkt wird sein, einen Service Locator zu haben . Eine einfache Implementierung ist ein Singleton mit einem schreibgeschützten Wörterbuch von Fabriken . Eine komplexere Implementierung könnte Reflexion verwenden, um die Fabriken zu erstellen, wenn Sie keine bereitgestellt haben.

Die Verwendung eines Singleton- oder statischen Service-Locators bedeutet jedoch, dass Sie so etwas wie var x = IoC.Resolve<?>jeden Ort ausführen, an dem Sie eine Instanz erstellen müssen. Dies fügt Ihrem Service Locator / Container / Injektor eine starke Kupplung hinzu. Das kann Unit-Tests tatsächlich schwieriger machen.

Sie möchten einen Injektor, den Sie instanziieren, und behalten ihn nur für die Verwendung auf dem Controller. Sie möchten nicht, dass es tief in den Code eindringt. Das könnte das Testen tatsächlich erschweren. Wenn ein Teil Ihres Codes es benötigt, um etwas zu instanziieren, sollte er eine Instanz (oder fast eine Factory) auf seinem Konstruktor erwarten.

Und wenn Sie viele Parameter im Konstruktor haben ... sehen Sie, ob Sie Parameter haben, die zusammen laufen. Möglicherweise können Sie Parameter zu Deskriptortypen zusammenführen (Werttypen idealerweise).

Theraot
quelle
Vielen Dank für diese klare Erklärung. Entsprechend Ihrer Meinung sollte ich eine Injektorklasse erstellen, die die Liste aller meiner Dienste enthält. Ich spritze nicht Service für Service ein. Ich spritze meine Injektorklasse. Diese Klasse verwaltet auch die einzelne Instanz jedes meiner Dienste?
Bastien Vandamme
@BastienVandamme Sie können Ihre Controller mit dem Injektor koppeln. Wenn Sie es also an den Controller weitergeben möchten, stimme ich zu. Und ja, es kann mit einer einzigen Instanz der Dienste umgehen. Auf der anderen Seite würde ich mir Sorgen machen, wenn der Controller den Injektor herumgibt ... er kann bei Bedarf Fabriken weitergeben. Tatsächlich besteht die Idee darin, die Dienste von der Initialisierung ihrer Abhängigkeiten zu trennen. Im Idealfall kennen die Dienste den Injektor nicht.
Theraot
"Die Verwendung von DI erleichtert das Testen, da Sie diese externen Systeme leicht verspotten können." Ich habe festgestellt, dass das Verspotten dieser Systeme beim Testen von Einheiten, ohne zuerst den Code zu minimieren, der irgendeine Verbindung zu ihnen hat, den Nutzen von Einheitentests erheblich verringert. Maximieren Sie zunächst die Menge an Code, die nur über Eingabe- / Ausgabetests getestet werden kann. Dann schauen Sie, ob Spott notwendig ist.
jpmc26
@ jpmc26 stimmte zu. Nachtrag: Wir sollten diese externen Systeme trotzdem isolieren.
Theraot
@ BastienVandamme Ich habe darüber nachgedacht. Ich denke, wenn Sie nicht die Fähigkeit verlieren, benutzerdefinierte Injektoren für die Weitergabe zu erstellen, sollte dies in Ordnung sein.
Theraot