Pregunta ¿Por qué necesito un contenedor IoC en lugar de un código DI directo? [cerrado]


He estado usando Inyección de dependencia (DI) por un tiempo, inyectando en un constructor, propiedad o método. Nunca sentí la necesidad de usar un Inversión de control (IoC) contenedor. Sin embargo, cuanto más leo, más presión siento de la comunidad para usar un contenedor IoC.

Jugué con contenedores .NET como StructureMap, NInject, Unidady Funq. Todavía no veo cómo un contenedor IoC va a beneficiar / mejorar mi código.

También tengo miedo de comenzar a usar un contenedor en el trabajo porque muchos de mis compañeros de trabajo verán código que no entienden. Muchos de ellos pueden ser reacios a aprender nuevas tecnologías.

Por favor, convencedme de que necesito usar un contenedor IoC. Voy a usar estos argumentos cuando hable con mis compañeros desarrolladores en el trabajo.


598


origen


Respuestas:


Wow, no puedo creer que Joel favorecería esto:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

Más allá de esto:

var svc = IoC.Resolve<IShippingService>();

Muchas personas no se dan cuenta de que su cadena de dependencias puede anidarse, y rápidamente se vuelve poco manejable para conectarlas manualmente. Incluso con las fábricas, la duplicación de su código simplemente no lo vale.

Los contenedores de IoC pueden ser complejos, sí. Pero para este simple caso, he demostrado que es increíblemente fácil.


De acuerdo, justifiquemos esto aún más. Supongamos que tiene algunas entidades u objetos de modelo que desea vincular a una IU inteligente. Esta interfaz de usuario inteligente (la llamaremos Shindows Morms) quiere que implemente INotifyPropertyChanged para que pueda cambiar el seguimiento y actualizar la IU según corresponda.

"OK, eso no suena tan duro", así que empiezas a escribir.

Comienzas con esto:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..y terminar con esta:

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Eso es un código de plomería desagradable, y sostengo que si estás escribiendo un código así a mano estás robando a tu cliente. Hay una forma mejor y más inteligente de trabajar.

¿Alguna vez oíste ese término, trabajas más inteligentemente, no más?

Bueno, imagina que surgió un tipo inteligente en tu equipo y dijo: "Aquí hay una manera más fácil"

Si haces que tus propiedades sean virtuales (cálmate, no es tan importante) entonces podemos tejido en ese comportamiento de propiedad automáticamente. (Esto se llama AOP, pero no se preocupe por el nombre, concéntrese en lo que va a hacer por usted)

Dependiendo de la herramienta de IoC que esté utilizando, podría hacer algo que se vea así:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

¡Maricón!  Todo ese manual INotifyPropertyChanged BS ahora se genera automáticamente para usted, en cada setter de propiedad virtual del objeto en cuestión.

Es esta magia? ! Si puede confiar en el hecho de que este código hace su trabajo, entonces puede saltear con seguridad toda esa propiedad envolviendo mugg-jumbo. Tienes problemas de negocios para resolver.

Algunos otros usos interesantes de una herramienta de IoC para hacer AOP:

  • Transacciones de bases de datos declarativas y anidadas
  • Unidad de trabajo declarativa y anidada
  • Explotación florestal
  • Condiciones previas / posteriores (diseño por contrato)

441



Estoy contigo, Vadim. Los contenedores de IoC toman un concepto simple, elegante y útil, y lo convierten en algo que debe estudiar durante dos días con un manual de 200 páginas.

Personalmente estoy perplejo de cómo la comunidad de IoC tomó una hermosa, elegante artículo de Martin Fowler y lo convirtió en un grupo de marcos complejos típicamente con manuales de 200-300 páginas.

Intento no ser crítico (HAHA!), Pero creo que las personas que usan contenedores IoC son (A) muy inteligentes y (B) carecen de empatía con las personas que no son tan inteligentes como lo son. Todo tiene perfecto sentido para ellos, por lo que tienen problemas para entender que muchos programadores comunes encontrarán confusos los conceptos. Es el maldición del conocimiento. Las personas que entienden los contenedores de IoC tienen problemas para creer que hay personas que no lo entienden.

La ventaja más valiosa de usar un contenedor IoC es que puede tener un interruptor de configuración en un lugar que le permite cambiar entre, por ejemplo, el modo de prueba y el modo de producción. Por ejemplo, supongamos que tiene dos versiones de sus clases de acceso a la base de datos ... una versión que registró agresivamente e hizo mucha validación, que usó durante el desarrollo, y otra versión sin registro o validación que fue extremadamente rápida para la producción. Es bueno poder cambiar entre ellos en un solo lugar. Por otro lado, este es un problema bastante trivial que se maneja fácilmente de una manera más simple sin la complejidad de los contenedores IoC.

Creo que si usa contenedores IoC, su código se vuelve, francamente, mucho más difícil de leer. La cantidad de lugares que debe observar para descubrir qué intenta hacer el código aumenta en al menos uno. Y en algún lugar del cielo un ángel grita.


138



Es de suponer que nadie te está forzando a usar un marco contenedor DI. Ya está usando DI para desacoplar sus clases y mejorar la capacidad de prueba, por lo que está obteniendo muchos de los beneficios. En resumen, está favoreciendo la simplicidad, que generalmente es algo bueno.

Si su sistema alcanza un nivel de complejidad donde el DI manual se convierte en una tarea rutinaria (es decir, aumenta el mantenimiento), péselo en comparación con la curva de aprendizaje del equipo de un marco de contenedor DI.

Si necesita más control sobre la administración de por vida de la dependencia (es decir, si siente la necesidad de implementar el patrón Singleton), mire los contenedores DI.

Si usa un contenedor DI, use solo las funciones que necesita. Omita el archivo de configuración XML y configúrelo en el código si eso es suficiente. Seguir con la inyección de constructor. Los conceptos básicos de Unity o StructureMap se pueden resumir en un par de páginas.

Hay una excelente publicación de blog de Mark Seemann sobre esto: Cuándo usar un contenedor DI


37



En mi opinión, el beneficio número uno de un IoC es la capacidad de centralizar la configuración de sus dependencias.

Si actualmente usa inyección de Dependencia, su código podría verse así:

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Si utilizó una clase de IoC estática, en oposición a la, en mi humilde opinión, los archivos de configuración más confusos, podría tener algo como esto:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Entonces, su clase de IoC estático se vería así, estoy usando Unity aquí.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}

32



Los Contenedores de IoC también son buenos para cargar dependencias de clase anidadas profundamente. Por ejemplo, si tiene el siguiente código usando Depedency Injection.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Si tuviera todas estas dependencias cargadas en el contenedor IoC, podría Resolver el servicio al cliente y todas las dependencias secundarias se resolverán automáticamente.

Por ejemplo:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}

32



Soy un fanático de la programación declarativa (miro cuántas preguntas de SQL respondo), pero los contenedores de IoC que he visto parecen demasiado arcanos por su propio bien.

... o quizás los desarrolladores de contenedores IoC son incapaces de escribir documentación clara.

... o de lo contrario ambos son fieles en un grado u otro.

No creo que el concepto de un contenedor de IoC es malo. Pero la implementación tiene que ser poderosa (es decir, flexible) lo suficiente como para ser útil en una amplia variedad de aplicaciones, pero simple y fácil de entender.

Probablemente sean seis de una y media docena de la otra. Una aplicación real (no un juguete o demo) es necesariamente compleja, lo que explica muchos casos de esquina y excepciones a las reglas. O encapsula esa complejidad en un código imperativo, o bien en un código declarativo. Pero debes representarlo en alguna parte.


28



Usar un contenedor se trata principalmente de cambiar de un imperativo / guionado estilo de inicialización y configuración a una declarativo uno. Esto puede tener algunos efectos beneficiosos diferentes:

  • Reducir bola de pelo rutinas de inicio del programa principal.
  • Permitir capacidades de reconfiguración en tiempo de despliegue bastante profundas.
  • Hacer que el estilo inyectable de dependencia sea el camino de menor resistencia para el nuevo trabajo.

Por supuesto, puede haber dificultades:

  • El código que requiere una administración compleja de inicio / apagado / ciclo de vida no se puede adaptar fácilmente a un contenedor.
  • Probablemente tengas que navegar por cuestiones personales, de proceso y cultura de equipo, pero entonces, es por eso que preguntaste ...
  • Algunos de los kits de herramientas se están convirtiendo rápidamente en pesados, lo que fomenta el tipo de dependencia profunda que muchos contenedores DI comenzaron como una reacción negativa.

27