wake-up-neo.net

Abhängigkeitsinjektion vs. Servicestandort

Ich bin gerade dabei, die Vor- und Nachteile zwischen DI und SL abzuwägen. Ich habe mich jedoch in der folgenden Zeile 22 wiedergefunden, was bedeutet, dass ich SL für alles verwenden sollte und nur einen IoC-Container in jede Klasse injizieren sollte.

DI Catch 22:

Einige Abhängigkeiten wie Log4Net passen einfach nicht zu DI. Ich nenne diese Meta-Abhängigkeiten und denke, dass sie für aufrufenden Code undurchsichtig sein sollten. Meine Begründung ist, dass, wenn eine einfache Klasse 'D' ursprünglich ohne Protokollierung implementiert wurde und dann immer mehr Protokollierung erforderlich ist, die abhängigen Klassen 'A', 'B' und 'C' diese Abhängigkeit jetzt irgendwie abrufen müssen 'A' bis 'D' (vorausgesetzt, A 'setzt sich aus' B 'zusammen,' B 'setzt sich aus' C 'zusammen usw.). Wir haben jetzt wichtige Codeänderungen vorgenommen, nur weil wir in einer Klasse protokollieren müssen.

Wir benötigen daher einen undurchsichtigen Mechanismus, um Meta-Abhängigkeiten zu erhalten. Zwei fallen mir ein: Singleton und SL. Ersteres hat bekannte Einschränkungen, vor allem in Bezug auf starre Scoping-Fähigkeiten: Im besten Fall verwendet Singleton eine Abstract Factory, die im Anwendungsbereich (dh in einer statischen Variablen) gespeichert ist. Dies ermöglicht eine gewisse Flexibilität, ist jedoch nicht perfekt.

Eine bessere Lösung wäre, einen IoC-Container in solche Klassen einzuspeisen und dann SL innerhalb dieser Klasse zu verwenden, um diese Meta-Abhängigkeiten aus dem Container aufzulösen.

Daher Punkt 22: Da der Klasse jetzt ein IoC-Container injiziert wird, können Sie ihn dann auch verwenden, um alle anderen Abhängigkeiten aufzulösen.

Ich würde mich sehr über Ihre Gedanken freuen :)

34

Da die Klasse nun mit einem IoC-Container injiziert wird, können Sie sie auch verwenden, um alle anderen Abhängigkeiten aufzulösen.

Durch Verwendung des Service Locator-Musters wird einer der Hauptpunkte der Abhängigkeitseinspritzung vollständig beseitigt. Der Punkt der Abhängigkeitsinjektion besteht darin, Abhängigkeiten explizit zu machen. Nachdem Sie diese Abhängigkeiten ausgeblendet haben, indem Sie sie nicht explizit in einem Konstruktor festlegen, wird keine vollständige Abhängigkeitsinjektion mehr durchgeführt.

Dies sind alles Konstruktoren für eine Klasse mit dem Namen Foo (die auf das Thema des Johnny Cash-Songs eingestellt ist):

Falsch:

public Foo() {
    this.bar = new Bar();
}

Falsch:

public Foo() {
    this.bar = ServiceLocator.Resolve<Bar>();
}

Falsch:

public Foo(ServiceLocator locator) {
    this.bar = locator.Resolve<Bar>();
}

Recht:

public Foo(Bar bar) {
    this.bar = bar;
}

Nur die letztere macht die Abhängigkeit von Bar explizit.

Was die Protokollierung angeht, gibt es eine gute Möglichkeit, dies zu tun, ohne dass dies in Ihren Domänencode eindringt (dies sollte nicht der Fall sein, aber wenn dies der Fall ist, verwenden Sie die Abhängigkeitseingangsperiode). Erstaunlicherweise können IoC-Container bei diesem Problem helfen. Starten Sie hier .

56
jason

Service Locator ist ein Anti-Pattern, das aus http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx aus exzellenten Gründen beschrieben wird. In Bezug auf die Protokollierung können Sie dies entweder wie eine Abhängigkeit behandeln und eine Abstraktion über den Konstruktor oder die Eigenschaftsinjektion einfügen. 

Der einzige Unterschied zu log4net besteht darin, dass der Typ des Anrufers erforderlich ist, der den Dienst verwendet. Ninject (oder einen anderen Container) verwenden Wie kann ich herausfinden, welcher Typ den Dienst anfordert? beschreibt, wie Sie dieses Problem lösen können (es verwendet Ninject, ist jedoch für jeden IoC-Container anwendbar). 

Alternativ können Sie sich die Protokollierung als Querschnittsthema vorstellen, das sich nicht mit Ihrem Geschäftslogik-Code vermischen lässt. In diesem Fall können Sie das Abhören verwenden, das von vielen IoC-Containern bereitgestellt wird. http://msdn.Microsoft.com/de-de/library/ff647107.aspx beschreibt die Verwendung von Abhören mit Unity. 

8
devdigital

Meine Meinung ist, dass es darauf ankommt. Manchmal ist einer besser und manchmal einer. Aber ich würde sagen, dass ich generell lieber DI bin. Dafür gibt es wenige Gründe.

  1. Wenn Abhängigkeiten irgendwie in die Komponente eingefügt werden, kann sie als Teil ihrer Schnittstelle behandelt werden. Daher ist es für den Benutzer der Komponente einfacher, diese Abhängigkeiten bereitzustellen, da sie sichtbar sind. Bei injiziertem SL oder statischem SL sind diese Abhängigkeiten verborgen und die Verwendung der Komponente ist etwas schwieriger.

  2. Injizierte Abhängigkeiten eignen sich besser für Komponententests, da Sie sie einfach nur verspotten können. Im Falle von SL müssen Sie Locator + Mock-Abhängigkeiten erneut einrichten. Es ist also mehr Arbeit.

5
Oleg Rudckivsky

Manchmal kann die Protokollierung mithilfe von AOP implementiert werden, sodass sie nicht mit der Geschäftslogik vermischt wird. 

Ansonsten stehen folgende Optionen zur Verfügung: 

  • verwenden Sie eine optionale Abhängigkeit (z. B. eine Setter-Eigenschaft), und für den Komponententest injizieren Sie keinen Logger. IOC Container sorgt für die automatische Einstellung für Sie, wenn Sie in der Produktion laufen.
  • Wenn Sie eine Abhängigkeit haben, die von fast jedem Objekt Ihrer App verwendet wird ("Logger" -Objekt ist das häufigste Beispiel), ist dies einer der wenigen Fälle, in denen das Anti-Muster für Singleton zu einer bewährten Methode wird. Einige Leute nennen diese "guten Singletons" einen Ambient Context: http://aabs.wordpress.com/2007/12/31/the-ambient-context-design-pattern-in-net/

Natürlich muss dieser Kontext konfigurierbar sein, damit Sie Stub/Mock für Komponententests verwenden können ..__ Eine andere vorgeschlagene Verwendung von AmbientContext besteht darin, den aktuellen Datums-/Uhrzeit-Provider dort abzulegen, sodass Sie ihn während des Komponententests stubben können und beschleunigt die Zeit, wenn Sie möchten.

4
frecil

Wir sind auf einen Kompromiss gestoßen: Verwenden Sie DI, bündeln Sie jedoch die Abhängigkeiten der obersten Ebene in einem Objekt, und vermeiden Sie die Umgestaltung der Umgebung, falls sich diese Abhängigkeiten ändern. 

Im folgenden Beispiel können wir 'ServiceDependencies' hinzufügen, ohne alle abgeleiteten Abhängigkeiten umgestalten zu müssen. 

Beispiel:

public ServiceDependencies{
     public ILogger Logger{get; private set;}
     public ServiceDependencies(ILogger logger){
          this.Logger = logger;
     }
}

public abstract class BaseService{
     public ILogger Logger{get; private set;}

     public BaseService(ServiceDependencies dependencies){
          this.Logger = dependencies.Logger; //don't expose 'dependencies'
     }
}


public class DerivedService(ServiceDependencies dependencies,
                              ISomeOtherDependencyOnlyUsedByThisService                       additionalDependency) 
 : base(dependencies){
//set local dependencies here.
}
1
BlackjacketMack

Ich habe das Google Guice DI-Framework in Java verwendet und festgestellt, dass es weitaus mehr als nur das Testen erleichtert. Zum Beispiel benötigte ich ein separates Protokoll pro application (nicht class), mit der zusätzlichen Voraussetzung, dass mein allgemeiner Bibliothekscode den Logger im aktuellen Aufrufkontext verwendet. Das Einspritzen des Loggers machte dies möglich. Allerdings musste der gesamte Bibliothekscode geändert werden: Der Logger wurde in die Konstruktoren eingefügt. Zunächst widersetzte ich mich diesem Ansatz wegen aller erforderlichen Codierungsänderungen. Schließlich erkannte ich, dass die Änderungen viele Vorteile hatten:

  • Der Code wurde einfacher
  • Der Code wurde wesentlich robuster
  • Die Abhängigkeiten einer Klasse wurden offensichtlich
  • Wenn es viele Abhängigkeiten gab, war dies ein deutlicher Hinweis darauf, dass für eine Klasse ein Refactoring erforderlich war
  • Statische Singletons wurden eliminiert
  • Die Notwendigkeit für Session- oder Kontextobjekte ist verschwunden
  • Multi-Threading wurde wesentlich einfacher, da der DI-Container so gebaut werden konnte, dass er nur einen Thread enthält. Dadurch wird eine unbeabsichtigte Kreuzkontamination vermieden

Unnötig zu sagen, ich bin jetzt ein großer Fan von DI und verwende es für alle außer den trivialsten Anwendungen.

1
sabra

Wenn in diesem Beispiel nur log4net als Abhängigkeit verwendet wird, müssen Sie nur Folgendes tun:

ILog log = LogManager.GetLogger(typeof(Foo));

Es gibt keinen Grund, die Abhängigkeit zu injizieren, da log4net eine differenzierte Protokollierung durch den Typ (oder eine Zeichenfolge) als Parameter bereitstellt.

DI ist auch nicht mit SL korreliert. IMHO dient ServiceLocator zum Auflösen optionaler Abhängigkeiten.

ZB: Wenn der SL eine ILog-Schnittstelle bietet, schreibe ich Protokollierungs-Daa.

Ich weiß, dass diese Frage ein wenig alt ist, ich dachte nur, ich würde meinen Input geben.

In Wirklichkeit sind Sie bei 9 von 10 Punkten nicht need SL und sollten sich auf DI verlassen. Es gibt jedoch Fälle, in denen Sie SL verwenden sollten. Ein Bereich, in dem ich SL finde (oder eine Variation davon), ist die Spieleentwicklung.

Ein weiterer Vorteil von SL (meiner Meinung nach) ist die Möglichkeit, internal-Klassen zu übergeben.

Unten ist ein Beispiel:

internal sealed class SomeClass : ISomeClass
{
    internal SomeClass()
    {
        // Add the service to the locator
        ServiceLocator.Instance.AddService<ISomeClass>(this);
    }

    // Maybe remove of service within finalizer or dispose method if needed.

    internal void SomeMethod()
    {
        Console.WriteLine("The user of my library doesn't know I'm doing this, let's keep it a secret");
    }
}

public sealed class SomeOtherClass
{
    private ISomeClass someClass;

    public SomeOtherClass()
    {
        // Get the service and call a method
        someClass = ServiceLocator.Instance.GetService<ISomeClass>();
        someClass.SomeMethod();
    }
}

Wie Sie sehen, hat der Benutzer der Bibliothek keine Ahnung, dass diese Methode aufgerufen wurde, weil wir keine DI hatten und nicht, dass wir sowieso könnten.

1
Mathew O'Dwyer

Dies ist der "Service Locator ist ein Anti-Pattern" von Mark Seeman. Ich könnte hier falsch liegen. Aber ich dachte nur, ich sollte auch meine Gedanken teilen. 

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

Die Process () - Methode für OrderProcessor folgt eigentlich nicht dem 'Inversion of Control'-Prinzip. Es verstößt auch gegen das Prinzip der einheitlichen Verantwortung auf der Methodenebene. Warum sollte eine Methode mit der Instanziierung der 

objekte (über neue oder beliebige S.L.-Klasse) muss es alles erreichen. 

Anstatt die Process () - Methode die Objekte erstellen zu lassen, kann der Konstruktor tatsächlich die Parameter für die jeweiligen Objekte haben (Abhängigkeiten lesen), wie unten gezeigt. Wie kann sich ein Service Locator von einem IOC unterscheiden? 

container. UND es wird auch beim Unit Testing helfen.

public class OrderProcessor : IOrderProcessor
{
    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
    {
        this.validator = validator; 
        this.shipper = shipper;
    }

    public void Process(Order order)
    {

        if (this.validator.Validate(order))
        {
            shipper.Ship(order);
        }
    }
}


//Caller
public static void main() //this can be a unit test code too.
{
var validator = Locator.Resolve<IOrderValidator>(); // similar to a IOC container 
var shipper = Locator.Resolve<IOrderShipper>();

var orderProcessor = new OrderProcessor(validator, shipper);
orderProcessor.Process(order);

}
1
Aniket Bhoyar

Benötigen Sie für DI einen festen Verweis auf die eingespritzte Baugruppe? Ich sehe niemanden, der darüber spricht. Bei SL kann ich meinem Resolver mitteilen, wo er meinen Typ dynamisch laden soll, wenn er aus einer config.json oder einer ähnlichen Datei benötigt wird. Wenn Ihre Assembly mehrere Tausend Typen und deren Vererbung enthält, müssen Sie mehrere Tausend kaskadierende Aufrufe an den Service Collection-Anbieter senden, um sie zu registrieren. Dort sehe ich viel Gerede. Die meisten sprechen über den Nutzen von DI und darüber, was es im Allgemeinen ist, wenn es darum geht, es in .net zu implementieren. Sie präsentierten eine Erweiterungsmethode zum Hinzufügen eines Verweises auf eine fest verknüpfte Typen-Assembly. Das ist für mich nicht sehr entkoppelnd.

0
FTLTraveller

Ich weiß, dass die Leute wirklich sagen, dass DI das einzige gute IOC Muster ist, aber ich verstehe das nicht. Ich werde versuchen, SL ein bisschen zu verkaufen. Ich werde das neue MVC Core-Framework verwenden, um Ihnen zu zeigen, was ich meine. Die ersten DI-Motoren sind sehr komplex. Was die Leute wirklich meinen, wenn sie DI sagen, ist die Verwendung eines Frameworks wie Unity, Ninject, Autofac ..., das alle schweren Aufgaben für Sie erledigt, wobei SL so einfach sein kann wie eine Werksklasse. Für ein kleines, schnelles Projekt ist dies eine einfache Möglichkeit, IOC zu tun, ohne einen ganzen Rahmen für richtiges DI zu lernen, sie sind vielleicht nicht so schwer zu lernen, aber dennoch ... Nun zu dem Problem, das DI werden kann. Ich werde ein Zitat aus MVC Core-Dokumenten verwenden. "" ASP.NET Core wurde von Grund auf so konzipiert, dass die Injektion von Abhängigkeiten unterstützt wird. " Die meisten Leute sagen, dass ungefähr 99% Ihrer Codebasis keine Kenntnis Ihres IoC-Containers haben sollten. Warum sollten sie also von Grund auf neu entwerfen müssen, wenn nur 1% des Codes davon wissen sollte, hat der alte MVC nicht DI unterstützt? Nun, das ist das große Problem von DI, es hängt von DI ab. Damit alles funktioniert, "WENN ES GETAN WERDEN MUSS", ist viel Arbeit nötig. Wenn Sie sich die neue Action Injection anschauen, hängt dies nicht von DI ab, wenn Sie das [FromServices]-Attribut verwenden. Jetzt sagen DI-Leute NEIN, dass Sie mit Factories nicht mit diesem Zeug gehen, aber wie Sie sehen können, haben nicht einmal Leute, die MVC gemacht haben, es richtig gemacht. Das Problem von DI ist in Filtern sichtbar. Schauen Sie sich an, was Sie tun müssen, um DI in einen Filter zu bekommen 

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation("Business action starting...");
            // perform some business logic work

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // perform some business logic work
            _logger.LogInformation("Business action completed.");
        }
    }
}

Wenn Sie SL verwendet hätten, hätten Sie dies mit var _logger = Locator.Get (); Und dann kommen wir zu den Ansichten. Bei allem guten Willen in Bezug auf DI mussten sie SL für die Ansichten verwenden. Die neue Syntax @inject StatisticsService StatsService stimmt mit var StatsService = Locator.Get<StatisticsService>(); überein. Der am meisten beworbene Teil von DI ist das Testen von Einheiten. Aber was die Leute machen, ist nur das Testen von Mock-Diensten ohne Zweck oder die Notwendigkeit, einen DI-Motor anzuschließen, um echte Tests durchzuführen. Und ich weiß, dass man alles schlecht machen kann, aber die Leute machen am Ende einen SL-Locator, auch wenn sie nicht wissen, was es ist. Wo nicht viele Leute DI machen, ohne vorher darüber zu lesen ... Mein größtes Problem bei DI ist, dass der Benutzer der Klasse sich der inneren Funktionsweise der Klasse in anderen bewusst sein muss, um sie verwenden zu können.
SL lässt sich gut einsetzen und hat vor allem einige Vorteile, was seine Einfachheit angeht.

0
Filip Cordas