2010-01-20 8 views
10

Esta pregunta es el resultado de un mensaje por Jeffery Palermo el código Cómo moverse ramificada y de inyección de dependencias http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/COI y constructor de sobre-inyección resolución anti-patrón

En su puesto, Jeffery tiene una clase (public class OrderProcessor : IOrderProcessor) que toma 2 interfaces en el constructor. Uno es un validador IOrderValidator y una interfaz IOrderShipper. Su código de método se bifurca después de usar solo métodos en la interfaz IOrderValidator y nunca usa nada en la interfaz IOrderShipper.

Sugiere crear una fábrica que llame a un método estático para obtener el delegado de la interfaz. Él está creando un nuevo objeto en su código refactorizado que parece innecesario.

Supongo que el quid de la cuestión es que estamos utilizando IoC para construir todos nuestros objetos, independientemente de si se están utilizando o no. Si instancia un objeto con 2 interfaces y tiene un código que podría bifurcarse para no usar uno de ellos, ¿cómo lo maneja?

En este ejemplo, suponemos _validator.Validate(order) siempre devuelve falso y el método IOrderShipper.Ship() nunca es llamado.

Código original:

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 
    } 
} 

Código refactorizado

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 
    } 
} 

Y aquí es el método que configura esta fábrica en el momento de puesta en marcha (Global.asax para ASP.NET):

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

presumiblemente, el código refactorizado no debe tomar el IOrderShipper en el constructor ... de lo contrario, ¿cuál es el sentido de la refactorización? –

+1

En la publicación que enlazó, su código refactorizado no tomó un IOrderShipper en el constructor, pero en su código refactorizado sí lo hace. No necesariamente estoy de acuerdo con Jeffrey Pallermo en este caso, pero es una distinción importante entre su implementación y la suya. –

+0

Supongo que he copiado el código incorrectamente. Pensé que sería más fácil ponerlo todo aquí = (Corregido –

Respuesta

22

Acabo de publicar un rebuttal of Jeffrey Palermos post.

En resumen, no debemos permitir que los detalles concretos de implementación influyan en nuestro diseño. Eso estaría violando el Principio de Sustitución de Liskov en la escala arquitectónica.

Una solución más elegante nos permite mantener el diseño mediante la introducción de un Orden de envío de carga diferida.

+3

+1 Estoy de acuerdo con su refutación. Este anti-patrón me parece incorrecto, porque su 'OrderProcessor' ahora depende de' IOrderShipper' que usted no informando a su consumidor acerca de. –

+0

Y otro +1 por mencionar el [Principio de Sustitución de Liskov] (http://en.wikipedia.org/wiki/Liskov_substitution_principle) – CrazyPyro

-2

Creo que el antipatrón es el uso excesivo de interfaces.

1

Estoy llegando tarde a una reunión, pero algunos puntos rápidos ...

Cumplir con el código de ramificación de la utilización de una sola dependencia, hay dos ramas para proponer:

  • Aplicación Prácticas de DDD, no tendría un OrderProcessor con una dependencia en IOrderValidator. En cambio, harías que la entidad Order() fuera responsable de su propia validación. O bien, cumpla con su IOrderValidator, pero tenga su dependencia dentro del OrderShipper() que se implementa, ya que devolverá cualquier código de error.
  • Asegúrese de que las dependencias que se inyectan estén construidas utilizando un enfoque de Singleton (y configuradas como Singleton en el contenedor de IoC que se utiliza). Esto resuelve cualquier problema de memoria.

Al igual que otra persona que mencionamos aquí, el punto es romper la dependencia de las clases concretas y acoplar libremente las dependencias mediante Dependency Injection con algún contenedor IoC en uso.

Esto hace que la futura refacturación e intercambio de código heredado sea mucho más fácil a gran escala al limitar la deuda técnica incurrida en el futuro. Una vez que tenga un proyecto de cerca de 500,000 líneas de código, con 3000 unidades de prueba, sabrá de primera mano por qué el IoC es tan importante.

Cuestiones relacionadas