2010-01-20 10 views
10

Diese Frage ergibt sich aus einem Beitrag von Jeffery Palermo ist wie http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/IoC und Konstruktor über Injektion anti-Musterauflösung

In seinem Posten um verzweigten Code und Dependency Injection zu bekommen, hat Jeffery eine Klasse (public class OrderProcessor : IOrderProcessor), die 2 Schnittstellen im Konstruktor benötigt. Einer ist ein Validator IOrderValidator und eine IOrderShipper Schnittstelle. Sein Methodencode verzweigt nach nur Methoden auf der IOrderValidator Schnittstelle und verwendet nie etwas auf der IOrderShipper Schnittstelle.

Er schlägt vor, eine Factory zu erstellen, die eine statische Methode aufruft, um den Delegaten der Schnittstelle zu erhalten. Er erstellt in seinem refaktorierten Code ein neues Objekt, das unnötig erscheint.

Ich denke, der Kern des Problems ist, dass wir IoC verwenden, um alle unsere Objekte zu erstellen, unabhängig davon, ob sie verwendet werden oder nicht. Wenn Sie ein Objekt mit 2 Schnittstellen instanziieren und Code haben, der verzweigen könnte, um keinen von ihnen zu verwenden, wie gehen Sie damit um?

In diesem Beispiel gehen wir davon _validator.Validate(order) immer false zurückgibt und die IOrderShipper.Ship() Methode aufgerufen wird nie.

Originalcode:

public class OrderProcessor : IOrderProcessor 
{ 
    private readonly IOrderValidator _validator; 
    private readonly IOrderShipper _shipper; 

    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) 
    { 
     _validator = validator; 
     _shipper = shipper; 
    } 

    public SuccessResult Process(Order order) 
    { 
     bool isValid = _validator.Validate(order); 
     if (isValid) 
     { 
      _shipper.Ship(order); 
     } 
     return CreateStatus(isValid); 
    } 

    private SuccessResult CreateStatus(bool isValid) 
    { 
     return isValid ? SuccessResult.Success : SuccessResult.Failed; 
    } 
} 

public class OrderShipper : IOrderShipper 
{ 
    public OrderShipper() 
    { 
     Thread.Sleep(TimeSpan.FromMilliseconds(777)); 
    } 

    public void Ship(Order order) 
    { 
     //ship the order 
    } 
} 

Überarbeitete-Code

public class OrderProcessor : IOrderProcessor 
{ 
    private readonly IOrderValidator _validator; 

    public OrderProcessor(IOrderValidator validator) 
    { 
     _validator = validator; 
    } 

    public SuccessResult Process(Order order) 
    { 
     bool isValid = _validator.Validate(order); 
     if (isValid) 
     { 
      IOrderShipper shipper = new OrderShipperFactory().GetDefault(); 
      shipper.Ship(order); 
     } 
     return CreateStatus(isValid); 
    } 

    private SuccessResult CreateStatus(bool isValid) 
    { 
     return isValid ? SuccessResult.Success : SuccessResult.Failed; 
    } 
} 

public class OrderShipperFactory 
{ 
    public static Func<IOrderShipper> CreationClosure; 
    public IOrderShipper GetDefault() 
    { 
     return CreationClosure(); //executes closure 
    } 
} 

Und hier ist die Methode, die diese Fabrik beim Start-up-Zeit (global.asax für ASP.NET) konfiguriert:

private static void ConfigureFactories() 
{ 
    OrderShipperFactory.CreationClosure = 
     () => ObjectFactory.GetInstance<IOrderShipper>(); 
} 
+0

vermutlich sollte der refaktorierte Code nicht den IOrderShipper im Konstruktor übernehmen ... sonst ist der Punkt des Refactorings? –

+1

In dem Post, den Sie verlinkt haben, hat sein refaktorierter Code keinen IOrderShipper im Konstruktor, aber in Ihrem refaktorierten Code ist dies der Fall. Ich stimme Jeffrey Pallermo in diesem Fall nicht unbedingt zu, aber es ist ein wichtiger Unterschied zwischen Ihrer Implementierung und seiner. –

+0

Ich denke, ich habe den Code falsch kopiert. Ich dachte, es wäre einfacher, alles hier zu setzen = (Korrigiert –

Antwort

22

Ich habe gerade eine rebuttal of Jeffrey Palermos post gepostet.

Kurz gesagt, wir sollten nicht zulassen, dass konkrete Implementierungsdetails unser Design beeinflussen. Das würde das Liskow-Substitutionsprinzip auf der architektonischen Skala verletzen.

Eine elegantere Lösung lässt uns das Design beibehalten, indem wir einen Lazy-loading OrderShipper einführen.

+3

+1 Ich stimme Ihrer Widerlegung zu. Dieses Anti-Muster scheint mir falsch, weil Ihr 'OrderProcessor' jetzt eine Abhängigkeit von 'IOrderShipper' hat, die Sie nicht sind Informieren Sie Ihre Verbraucher über. –

+0

Und noch ein +1 für die Erwähnung des [Liskow Substitution Prinzip] (http://en.wikipedia.org/wiki/Liskov_substitution_principle) – CrazyPyro

-2

Ich denke, das Anti-Muster hier ist die Überbeanspruchung von Schnittstellen.

1

Ich bin spät für ein Treffen, aber ein paar schnelle Punkte ...

Das Festhalten an dem Code ausgeführt wird nur eine Abhängigkeit der Verwendung von Verzweigungen gibt es zwei Zweige vorschlagen:

  • Anwendung DDD-Praktiken, Sie hätten keinen OrderProcessor mit einer Abhängigkeit von IOrderValidator. Stattdessen würden Sie die Entität Order() für ihre eigene Validierung verantwortlich machen. Oder bleiben Sie bei Ihrem IOrderValidator, aber haben Sie eine Abhängigkeit in der OrderShipper(), die implementiert wird - da es alle Fehlercodes zurückgibt.
  • Stellen Sie sicher, dass die Abhängigkeiten, die injiziert werden, mithilfe eines Singleton-Ansatzes erstellt werden (und als Singleton im verwendeten IoC-Container konfiguriert sind). Dies behebt Speicherprobleme.

Wie jemand anderes, das hier erwähnt, ist der Punkt, um die Abhängigkeit von konkreten Klassen und lose Kopplung der Abhängigkeiten mit Dependency Injection mit einigen IoC Behälter in Gebrauch zu brechen.

Dies macht das zukünftige Refactoring und den Austausch von Legacy-Code erheblich einfacher, indem die in Zukunft anfallenden technischen Schulden begrenzt werden. Sobald Sie ein Projekt in der Nähe von 500.000 Zeilen Code mit 3000 Unit-Tests haben, werden Sie aus erster Hand wissen, warum IoC so wichtig ist.