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


598

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

Jugué con contenedores .NET como StructureMap , NInject , Unity y Funq . Todavía no veo cómo un contenedor de IoC 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 un código que no entienden. Muchos de ellos pueden ser reacios a aprender nuevas tecnologías.

Por favor, convencerme de que necesito usar un contenedor de IoC. Usaré estos argumentos cuando hable con mis colegas desarrolladores en el trabajo.


66
Buena pregunta. Estoy respondiendo esto en comentarios ya que soy muy nuevo en la idea del COI. Parece la idea de componentes plug and play y acoplamiento flojo. Ya sea que necesite utilizar algún otro componente en lugar del actual, se basa en la necesidad. El uso de IOC es para preparar el código para tal cambio, si surge IMO.
shahkalpesh

3
@shahkalpesh pero puedo lograr un acoplamiento suelto con una DI simple. Sin embargo, veo su punto en caso de que use el archivo de configuración. Sin embargo, no estoy seguro de si me gusta usar el archivo de configuración. Los archivos de configuración son muy detallados, tienen dificultades para refactorizar y cambiar entre varios archivos.
Vadim

110
solo agrega un comentario, ya que parece que está buscando razones para usar IoC principalmente; pero hay algo más que decir sobre su problema. No puede escribir su código al nivel de la peor persona de su equipo o por temor a que no quiera aprender. Usted tiene la responsabilidad no solo de ser un profesional, sino también de su futuro para crecer y su equipo no debe detenerlo.
Kevin Sheffield

44
@Kevin, en realidad estamos usando IoC. No fue tan difícil como pensar enseñar a todos sobre IoC.
Vadim el

21
No tengo idea de por qué los moderadores cierran preguntas útiles y que han votado masivamente. ¿Quizás en este caso, Will no entiende la pregunta o no entiende para qué usa la gente el intercambio de pila? Si "no es una buena opción para el formato de preguntas y respuestas de stackexchange", entonces tal vez considere que su política es incorrecta y no todas estas cientos de preguntas útiles con cientos de votos positivos y respuestas útiles.
NickG

Respuestas:


441

Wow, no puedo creer que Joel esté a favor de 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 difícil de manejar para conectarlas manualmente. Incluso con las fábricas, la duplicación de su código simplemente no vale la pena.

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


Bien, 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 hacer un seguimiento de cambios y actualizar la interfaz de usuario en consecuencia.

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

Empiezas 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 esto :

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

Es un código de plomería repugnante, y mantengo que si está escribiendo un código así a mano, le está robando a su cliente . Hay una forma mejor y más inteligente de trabajar.

¿Alguna vez escuchó ese término, trabaja de manera más inteligente, no más duro?

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

Si hace que sus propiedades sean virtuales (tranquilícese, no es un gran problema), entonces podemos incorporar 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é usando, podría hacer algo como esto:

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

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

¿Es esto magia? SI ! Si puede confiar en el hecho de que este código hace su trabajo, puede omitir con seguridad toda esa propiedad que envuelve mumbo-jumbo. Tienes problemas comerciales que resolver.

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

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

28
Vaya, olvidaste hacer todas las propiedades virtuales, y no funcionó. ¿Es el tiempo que necesitabas para depurar este robo de tu cliente también? (Disculpas si no estás usando DynamicProxy.: P)
Thom

17
Sacrificas MUCHA claridad usando el contenedor INotifyPropertyChanged. Si le toma más de unos minutos escribir esa plomería para comenzar, entonces está en el negocio equivocado. ¡Menos no es más cuando se trata de la verbosidad de su código!
superado el

39
@ entendido - Estoy completamente en desacuerdo. Primero, ¿dónde importa la claridad en este ejemplo? Más cercano al consumidor. El consumidor solicita explícitamente INotifyPropertyChanged. El hecho de que podamos crear este tipo de código con generación de código micro o macro no significa que sea una buena idea escribirlo. Esa mugre INotifyPropertyChanged es simplemente rutinaria, mundana, de plomería y en realidad menoscaba la legibilidad de la clase en cuestión.
Ben Scheirman el

31
Marcado porque está confundiendo el patrón de Inyección de dependencias y el patrón Localizador de servicios. El primero está destinado a ser utilizado en lugar del segundo. Por ejemplo, un objeto (o de hecho todo el sistema) nunca debería llamar a Resolve (...) para obtener una instancia de IShippingService. Los objetos que necesitan un IShippingService deben recibir una referencia a la instancia que deben usar cuando se construyen, y alguna capa superior es responsable de conectar los dos objetos entre sí.
Nat

30
Sea lo que sea (IoC, DynamicProxy, AOP o magia negra ), ¿alguien puede vincularme a un artículo concreto / documentación específica en un marco que proporcione más detalles para lograr esto?
fostandy

138

Estoy contigo, Vadim. Los contenedores 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ó un artículo hermoso y elegante de Martin Fowler y lo convirtió en un conjunto de marcos complejos, típicamente con manuales de 200-300 páginas.

Intento no juzgar (¡JAJA!), Pero creo que las personas que usan contenedores de IoC son (A) muy inteligentes y (B) carecen de empatía por las personas que no son tan inteligentes como ellos. Todo tiene mucho sentido para ellos, por lo que tienen problemas para comprender que muchos programadores comunes encontrarán los conceptos confusos. Es la maldición del conocimiento . Las personas que entienden los contenedores de IoC tienen problemas para creer que hay personas que no lo entienden.

El beneficio más valioso 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, suponga que tiene dos versiones de sus clases de acceso a la base de datos ... una versión que se registró de forma agresiva e hizo mucha validación, que utilizó 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. El número de lugares que tiene que mirar para descubrir qué está tratando de hacer el código aumenta en al menos uno. Y en algún lugar del cielo un ángel clama.


81
Creo que tienes tus hechos mezclados un poco, Joel. IoC y contenedores vinieron primero, luego vino el tratado de Martin, no al revés.
Glenn Block

55
La mayoría de los contenedores te permiten comenzar muy rápido. Con autofac, mapa de estructura, unidad, ninject, etc., debería poder hacer que el contenedor funcione en unos 5 minutos. Sí, tienen funciones avanzadas, pero no las necesita para despegar.
Glenn Block

58
Joel, solía pensar igual que tú. Luego, literal y seriamente, pasé cinco minutos para descubrir cómo ejecutar la configuración más básica y me sorprendió la regla 80/20 en acción. Hace que el código sea mucho más limpio, especialmente en los casos simples.
Justin Bozonier

55
He estado programando durante menos de dos años y leí el wiki de Ninject y lo obtuve. He estado usando DI en muchos lugares desde entonces. ¿Viste eso? Menos de dos años y leyendo algunas páginas en una wiki. No soy un mago ni nada. No me considero un genio, pero me fue fácil de entender. No puedo imaginar que alguien que realmente entiende a OO no pueda comprenderlo. Además, ¿a qué manuales de 200-300 páginas se refiere? Nunca los he visto. Todos los contenedores de IoC que he usado son bastante cortos y van al grano.
JR Garcia

35
+1 bien escrito y pensado. Una cosa que creo que mucha gente no se da cuenta es que agregar un marco o tal para manejar "mágicamente" algo agrega automáticamente complejidad y limitaciones a un sistema. A veces vale la pena, pero a menudo se pasa algo por alto y se libera entropía en el código que intenta solucionar un problema impuesto por limitaciones. Puede ahorrar tiempo por adelantado, pero las personas tienen que darse cuenta de que puede costar 100 veces más adelante tratar de solucionar un problema oscuro que solo un puñado de personas realmente tiene suficiente conocimiento para resolver.
kemiller2002

37

Presumiblemente, nadie te está obligando a usar un marco contenedor DI. Ya está utilizando DI para desacoplar sus clases y mejorar la capacidad de prueba, por lo que obtiene muchos de los beneficios. En resumen, estás favoreciendo la simplicidad, que generalmente es algo bueno.

Si su sistema alcanza un nivel de complejidad en el que la DI manual se convierte en una tarea (es decir, aumenta el mantenimiento), compárelo con la curva de aprendizaje en equipo de un marco de contenedor DI.

Si necesita más control sobre la gestión de la vida útil de la dependencia (es decir, si siente la necesidad de implementar el patrón Singleton), observe 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 código si es suficiente. Se adhieren a la inyección del constructor. Los conceptos básicos de Unity o StructureMap se pueden resumir en un par de páginas.

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


Muy buena respuesta. Lo único en lo que diferiría en mi opinión es si la inyección manual NUNCA puede considerarse menos compleja o no una tarea.
wekempf

+1. Una de las mejores respuestas aquí. Gracias también por el enlace del artículo del blog.
stakx - ya no contribuye el

1
+1 para el punto de vista equilibrado. Los contenedores de IoC no siempre son buenos, y no siempre son malos. Si no ve una necesidad, no la use. Solo sepa que la herramienta está ahí si ve una necesidad.
Phil

32

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 está usando la 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, a diferencia de, 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ática 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.
   }
}

11
Ben, de lo que estás hablando es de la ubicación del servicio, no de la inyección de dependencia, hay una diferencia. Siempre prefiero el primero sobre el último. stevenharman.net/blog/archive/2009/09/25/…
stevenharman

23
En lugar de hacer IoC.Resolve <T> en su constructor predeterminado, debe aprovechar la capacidad del contenedor IOC para construir el objeto por usted. Deshágase de ese constructor vacío y deje que el contenedor IOC construya el objeto por usted. var presentador = IoC.Resolve <CustomerPresenter> (); voila! Todas las dependencias ya están conectadas.
Ben Scheirman

12
La desventaja de la centralización de este tipo de configuración es la descontextualización del conocimiento. Joel señala este problema al decir, "francamente, el código se vuelve mucho más difícil de leer".
Scott Bellware

66
@sbellware, ¿qué conocimiento? La interfaz que se toma como una dependencia primaria sirve como un contrato y debería ser suficiente para transmitir su propósito y comportamiento, ¿no? Supongo que puede estar refiriéndose al conocimiento obtenido de la espeleología en la implementación del contrato, en cuyo caso necesitaría rastrear la configuración adecuada. Confío en una computadora (R # en VS, IntelliJ, etc.) para eso, ya que son excelentes para navegar nodos y bordes de un gráfico; Reservo la pila de mi cerebro para el contexto del sitio de trabajo en el que estoy enfocado actualmente.
stevenharman

77
Steve, conozco el texto de .NET y caminé esa caminata durante muchos años. También tengo una familiaridad íntima con otros modos y formas, y entiendo visceralmente que existen compensaciones reales. Sé cómo y cuándo hacer esas compensaciones, pero la diversidad en mi vida laboral al menos me permite comprender tangiblemente la perspectiva de Joel. En lugar de darme lecciones sobre herramientas y trucos que he estado usando durante más tiempo que la mayoría de los monocultivos de .NET, dime algo que no sé. Reservo la pila de mi cerebro para el pensamiento crítico y diverso en lugar de la estasis y la ortodoxia alt.net.
Scott Bellware

32

Los contenedores de IoC también son buenos para cargar dependencias de clase profundamente anidadas. Por ejemplo, si tenía el siguiente código usando Inyección de dependencia.

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 tenía todas estas dependencias cargadas en un contenedor de IoC, podría resolver el CustomerService 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.
}

Pero la clase IoC se llama utilizando el antipatrón del localizador de servicios. ¿Se debe pasar el contenedor utilizando el constructor DI en lugar de llamar a la resolución estática T <T> ()?
Michael Freidgeim

@bendewey Su ejemplo implica que pasa automáticamente los argumentos a los constructores para el nuevo CustomerService y el nuevo CustomerRepository. ¿Es así como funciona esto? ¿Qué sucede si también tiene otros parámetros adicionales?
Brain2000

28

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

... o tal vez los desarrolladores de contenedores IoC no pueden escribir documentación clara.

... o ambos son verdaderos en un grado u otro.

No creo que el concepto de un contenedor IoC sea malo. Pero la implementación debe ser lo suficientemente potente (es decir, flexible) 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 una demostración) está destinada a ser compleja, y representa muchos casos de esquina y excepciones a las reglas. O encapsula esa complejidad en código imperativo, o bien en código declarativo. Pero tienes que representarlo en alguna parte.


55
Me gusta su observación sobre la inevitable presencia de la complejidad. La diferencia entre el cableado de dependencia manual y el basado en contenedores es que al usar un contenedor, puede enfocarse en cada componente individualmente y, por lo tanto, administrar la creciente complejidad de una manera bastante lineal. Los sistemas con cableado de dependencia manual son más difíciles de evolucionar, ya que el acto de configurar un componente es difícil de aislar de la configuración de todo lo demás.
Nicholas Blumhardt

Me gusta AspectJ. No es Java, pero tampoco es XML. Es una especie de IS de Java, en el que se siente y se ve como Java por ser una extensión del mismo. Prefiero mantener mis aspectos fuera de mis POJO y en mi lógica de negocios. Casi me gustaría mantener mis IoC también. Hay demasiado XML en las herramientas de cableado, en mi humilde opinión. Si tengo que tener archivos de configuración, permítales que sean scripts de BeanShell. / Java, aplica tus workalikes .Net aquí ->.
Chris K

2
Ciertamente entiendo su respuesta, pero creo que una gran parte del problema es que muchos de los marcos de IoC (y, realmente, muchos de los otros marcos para otras cosas también) se describen y documentan para otros que ya están muy familiarizados con el Conceptos necesarios y terminología. Estoy aprendiendo mucho sobre esto después de haber heredado el código de otro programador. Me cuesta entender por qué el código usa IoC cuando los 'gráficos de objetos' son bastante pequeños y no hay una cobertura de prueba real del código. Como programador de SQL, probablemente pueda apreciar mejor el uso de IoC con un ORM.
Kenny Evitt

1
@KennyEvitt, buen punto, parecería prematuro usar un marco de IoC si uno tiene una aplicación simple y ni siquiera se ha molestado en tener una buena cobertura de prueba.
Bill Karwin

27

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

  • Reducción de las rutinas de inicio del programa principal de hairball .
  • Habilitando capacidades de reconfiguración de tiempo de implementación bastante profundas
  • Hacer que el estilo inyectable de dependencia sea el camino de menor resistencia para nuevos trabajos.

Por supuesto, puede haber dificultades:

  • El código que requiere una gestión compleja de inicio / apagado / ciclo de vida puede no adaptarse fácilmente a un contenedor.
  • Probablemente tendrá que navegar por cualquier problema de cultura personal, de proceso y de equipo, pero entonces, es por eso que preguntó ...
  • 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 violenta.

23

Me parece que ya construyó su propio contenedor de IoC (utilizando los diversos patrones que describió Martin Fowler) y se pregunta por qué la implementación de otra persona es mejor que la suya.

Entonces, tienes un montón de código que ya funciona. Y se preguntan por qué querrían reemplazarlo con la implementación de otra persona.

Ventajas para considerar un contenedor de IoC de terceros

  • Tienes errores corregidos gratis
  • El diseño de la biblioteca puede ser mejor que el tuyo.
  • Es posible que las personas ya estén familiarizadas con la biblioteca en particular.
  • La biblioteca puede ser más rápida que la tuya.
  • Es posible que tenga algunas características que desearía implementar pero que nunca tuvo tiempo (¿tiene un localizador de servicios?)

Contras

  • Te introducen errores, gratis :)
  • El diseño de la biblioteca puede ser peor que el tuyo.
  • Tienes que aprender una nueva API
  • Demasiadas funciones que nunca usarás
  • Por lo general, es más difícil depurar el código que no escribió
  • Migrar desde un contenedor de IoC anterior puede ser tedioso

Por lo tanto, compara tus ventajas con tus desventajas y toma una decisión.


Estoy de acuerdo con usted, Sam: DI es un tipo de IoC, por lo que si el póster original está haciendo DI, ya están haciendo IoC, por lo que la verdadera pregunta es por qué hacer el suyo.
Jamie Love

3
Estoy en desacuerdo. El COI es una forma de hacer DI. Los contenedores IOC hacen DI al tomar el control de la instanciación de tipos usando la reflexión dinámica de tiempo de ejecución para satisfacer e inyectar dependencias. El OP está sugiriendo que está haciendo DI estática y está reflexionando sobre por qué necesita intercambiar los beneficios de la compilación de tiempo comprobando los beneficios a menudo asociados con la reflexión dinámica del tiempo de ejecución del COI.
Maxm007

17

Creo que la mayor parte del valor de una IoC se obtiene mediante el uso de DI. Como ya lo está haciendo, el resto del beneficio es incremental.

El valor que obtenga dependerá del tipo de aplicación en la que esté trabajando:

  • Para múltiples inquilinos, el contenedor de IoC puede ocuparse de parte del código de infraestructura para cargar diferentes recursos del cliente. Cuando necesite un componente que sea específico del cliente, use un selector personalizado para manejar la lógica y no se preocupe por su código de cliente. Ciertamente puede construir esto usted mismo, pero aquí hay un ejemplo de cómo un IoC puede ayudar.

  • Con muchos puntos de extensibilidad, IoC se puede usar para cargar componentes desde la configuración. Es algo común de construir, pero el contenedor proporciona herramientas.

  • Si desea utilizar AOP para algunos problemas transversales, IoC proporciona enlaces para interceptar invocaciones de métodos. Esto se hace con menos frecuencia ad-hoc en proyectos, pero el IoC lo hace más fácil.

He escrito una funcionalidad como esta antes, pero si necesito alguna de estas características ahora, preferiría usar una herramienta prefabricada y probada si se ajusta a mi arquitectura.

Como lo mencionaron otros, también puede configurar centralmente qué clases desea usar. Aunque esto puede ser algo bueno, tiene un costo de dirección errónea y complicaciones. Los componentes centrales para la mayoría de las aplicaciones no se reemplazan mucho, por lo que la compensación es un poco más difícil de hacer.

Utilizo un contenedor de IoC y aprecio la funcionalidad, pero tengo que admitir que he notado la compensación: mi código se vuelve más claro a nivel de clase y menos claro a nivel de aplicación (es decir, visualizando el flujo de control).


16

Soy un adicto al COI en recuperación. Me resulta difícil justificar el uso de COI para la DI en la mayoría de los casos en estos días. Los contenedores IOC sacrifican la verificación del tiempo de compilación y supuestamente a cambio le brindan una configuración "fácil", una gestión de por vida compleja y el descubrimiento sobre la marcha de dependencias en tiempo de ejecución. Encuentro que la pérdida de tiempo de compilación y las excepciones y el tiempo de ejecución resultantes no valen la pena en la gran mayoría de los casos. En aplicaciones de grandes empresas, pueden hacer que sea muy difícil seguir lo que está sucediendo.

No compro el argumento de centralización porque también puede centralizar la configuración estática muy fácilmente al usar una fábrica abstracta para su aplicación y diferir religiosamente la creación de objetos a la fábrica abstracta, es decir, hacer una DI adecuada.

¿Por qué no hacer DI sin magia estática así:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

La configuración de su contenedor es su implementación de fábrica abstracta, sus registros son implementaciones de miembros abstractos. Si necesita una nueva dependencia singleton, simplemente agregue otra propiedad abstracta a la fábrica abstracta. Si necesita una dependencia transitoria, simplemente agregue otro método e inyéctelo como Func <>.

Ventajas:

  • Toda la configuración y creación de objetos está centralizada.
  • La configuración es solo código
  • La comprobación del tiempo de compilación facilita el mantenimiento, ya que no puede olvidarse de actualizar sus registros.
  • Sin magia de reflexión en tiempo de ejecución

Recomiendo a los escépticos que prueben el próximo proyecto de campo verde y honestamente pregúntense en qué punto necesita el contenedor. Es fácil tener en cuenta un contenedor IOC más adelante, ya que solo está reemplazando una implementación de fábrica con un módulo de configuración de contenedor IOC.


Esto no me parece un argumento contra IoC, sino un argumento contra los marcos configurables de IoC. ¿ Sus fábricas aquí no se reducen a simples IoC codificadas ?
Mr.Mindor

Creo que cuando el póster mencionaba IoC, se quería decir un marco contenedor de IoC. Gracias por la redacción original, esto me ayuda a convencerme de no usar un marco. Entonces, si es configurable en pruebas y en código de producción, ¿cuál es la ventaja de un marco "configurable"?
Huggie

Desde mi comprensión de la terminología, Inversion Of Control significa encontrar dependencias en tiempo de ejecución. Entonces, sí, se podría decir que la fábrica es básicamente un contenedor codificado o estático, pero no es un contenedor IOC. Los tipos se resuelven en tiempo de compilación. Es un argumento en contra del uso de marcos configurables que usan búsquedas de diccionario para resolver tipos en tiempo de ejecución.
Maxm007

14

El mayor beneficio de usar contenedores IoC para mí (personalmente, uso Ninject) ha sido eliminar la transferencia de configuraciones y otros tipos de objetos de estado global.

No programo para la web, la mía es una aplicación de consola y en muchos lugares en lo profundo del árbol de objetos necesito tener acceso a la configuración o metadatos especificados por el usuario que se crean en una rama completamente separada del árbol de objetos. Con IoC, simplemente le digo a Ninject que trate las configuraciones como un singleton (porque siempre hay solo una instancia de ellas), solicite las configuraciones o el diccionario en el constructor y listo ... ¡aparecen mágicamente cuando los necesito!

Sin usar un contenedor de IoC, tendría que pasar la configuración y / o metadatos a través de 2, 3, ..., n objetos antes de que el objeto lo necesitara.

Hay muchos otros beneficios para los contenedores DI / IoC ya que otras personas han detallado aquí y pasar de la idea de crear objetos a solicitar objetos puede ser alucinante, pero usar DI fue muy útil para mí y para mi equipo, así que tal vez puedan agregarlo a tu arsenal!


2
Esta es una gran ventaja. No puedo creer que nadie más lo haya mencionado antes. Sin la necesidad de pasar o almacenar objetos temporales, el código es mucho más limpio.
Finglas

1
Los objetos globales de @qble funcionan muy bien ... si desea que su base de código sea una pesadilla estrechamente acoplada y no comprobable que se vuelve cada vez más difícil de modificar con el tiempo. buena suerte con eso.
Jeffrey Cameron

2
@JeffreyCameron IoC Container es un objeto global. No elimina tus dependencias globales.
qble

3
Es cierto, sin embargo, el contenedor IoC es transparente para el resto de la aplicación. Lo llama una vez al inicio para obtener una instancia y luego es lo último que ve. Con los objetos globales, su código está lleno de dependencias difíciles
Jeffrey Cameron,

1
@qble usa fábricas para crear instancias transitorias sobre la marcha. Las fábricas son servicios únicos. Si las instancias transitorias necesitan algo del contenedor IOC, luego conéctelas a la fábrica y haga que la fábrica pase la dependencia a la instancia transitoria.
Jeffrey Cameron

14

Los marcos de IoC son excelentes si quieres ...

  • ... tirar la seguridad de tipo. Muchos (¿todos?) Marcos de IoC te obligan a ejecutar el código si quieres estar seguro de que todo está conectado correctamente. "¡Hey! ¡Espero tener todo configurado para que mis inicializaciones de estas 100 clases no fallen en la producción, arrojando excepciones de puntero nulo!"

  • ... ensucia tu código con globales (los marcos de trabajo de IoC son todos sobre el cambio de estados globales).

  • ... escriba código malo con dependencias poco claras que es difícil de refactorizar ya que nunca sabrá qué depende de qué.

El problema con IoC es que las personas que los usan solían escribir código como este

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

lo cual obviamente es defectuoso ya que la dependencia entre Foo y Bar está conectada. Luego se dieron cuenta de que sería mejor escribir código como

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

lo cual también es defectuoso, pero es menos obvio. En Haskell el tipo de Foo()seríaIO Foo pero realmente no desea la parte- IOy debería ser una señal de advertencia de que algo está mal con su diseño si lo tiene.

Para deshacerse de él (la parte IO), obtenga todas las ventajas de los marcos IoC y ninguno de sus inconvenientes podría utilizar una fábrica abstracta.

La solución correcta sería algo así como

data Foo = Foo { apa :: Bar }

o tal vez

data Foo = forall b. (IBar b) => Foo { apa :: b }

e inyectar (pero no lo llamaría inyectar) Bar.

Además: vea este video con Erik Meijer (inventor de LINQ) donde dice que DI es para personas que no saben matemáticas (y no podría estar más de acuerdo): http://www.youtube.com/watch?v = 8Mttjyf-8P4

A diferencia del Sr. Spolsky, no creo que las personas que usan IoC-frameworks sean muy inteligentes, simplemente creo que no saben matemáticas.


99
deseche la seguridad de tipo, excepto que su ejemplo sigue siendo de tipo seguro, esa es la interfaz. No estás pasando tipos de objetos, sino el tipo de interfaz. Y la seguridad de tipo no elimina las excepciones de referencia nula de todos modos, puede pasar felizmente las referencias nulas como parámetros de tipo seguro, eso no es un problema de seguridad de tipo en absoluto.
blowdart

Desecha la seguridad de tipos, ya que no sabe si el tipo está conectado o no en el contenedor de IoC (el tipo de IoC <IBar> no IBarlo es, de hecho lo es IO IBar). Y sí, en el ejemplo de Haskell No puedo obtener una referencia nula.
finnsson

La seguridad de tipo no se trata realmente de un objeto conectado, al menos no en Java / C #, se trata simplemente de que el objeto sea del tipo correcto. Y ConcreteClass myClass = null sigue siendo del tipo correcto. Si desea hacer cumplir no nulo, entonces entran en juego los contratos de código.
blowdart

2
Estoy de acuerdo con su primer punto hasta cierto punto, me gustaría una explicación del segundo y el tercero nunca ha sido un problema para mí. Su muestra de C # usa el contenedor IoC como un localizador de servicios, un error común. Y comparando C # vs Haskell = manzanas vs naranjas.
Mauricio Scheffer

2
No tirar la seguridad de tipografía. Algunos (si no la mayoría) de contenedores DI le permiten verificar el gráfico completo del objeto después del proceso de registro. Al hacer esto, puede evitar que la aplicación se inicie en una configuración incorrecta (falla rápidamente) y mis aplicaciones siempre tienen algunas pruebas unitarias que llaman a la configuración de producción para permitirme encontrar estos errores durante las pruebas. Aconsejaría a todos los que usan un contenedor de IoC que escriban algunas pruebas unitarias que aseguren que todos los objetos de nivel superior se puedan resolver.
Steven

10

Descubrí que la implementación correcta de la Inyección de dependencias tiende a obligar a los programadores a usar una variedad de otras prácticas de programación que ayudan a mejorar la capacidad de prueba, la flexibilidad, la capacidad de mantenimiento y la escalabilidad del código: prácticas como el Principio de responsabilidad única, Separaciones de preocupaciones y codificación contra las API. Parece que me veo obligado a escribir más clases y métodos modulares de tamaño de bocado, lo que hace que el código sea más fácil de leer, ya que puede tomarse en trozos de tamaño de bocado.

Pero también tiende a crear árboles de dependencia bastante grandes, que se manejan mucho más fácilmente a través de un marco (especialmente si usa convenciones) que a mano. Hoy quería probar algo realmente rápido en LINQPad, y pensé que sería demasiado molesto crear un núcleo y cargarlo en mis módulos, y terminé escribiendo esto a mano:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

En retrospectiva, hubiera sido más rápido usar el marco de IoC, ya que los módulos definen prácticamente todo esto por convención.

Después de pasar algún tiempo estudiando las respuestas y los comentarios sobre esta pregunta, estoy convencido de que las personas que se oponen al uso de un contenedor de IoC no están practicando una verdadera inyección de dependencia. Los ejemplos que he visto son de prácticas que comúnmente se confunden con la inyección de dependencia. Algunas personas se quejan de la dificultad de "leer" el código. Si se hace correctamente, la gran mayoría de su código debe ser idéntico cuando usa DI a mano como cuando usa un contenedor de IoC. La diferencia debería residir completamente en unos pocos "puntos de lanzamiento" dentro de la aplicación.

En otras palabras, si no le gustan los contenedores de IoC, probablemente no esté haciendo la inyección de dependencia de la forma en que se supone que debe hacerse.

Otro punto: la inyección de dependencia realmente no se puede hacer a mano si usa la reflexión en cualquier lugar. Si bien odio lo que hace la reflexión al código de navegación, debe reconocer que hay ciertas áreas donde realmente no se puede evitar. ASP.NET MVC, por ejemplo, intenta crear una instancia del controlador mediante la reflexión en cada solicitud. Para hacer la inyección de dependencia a mano, tendría que hacer que cada controlador sea una "raíz de contexto", así:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Ahora compare esto con permitir que un marco DI lo haga por usted:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

Usando un marco DI, tenga en cuenta que:

  • Puedo hacer una prueba unitaria de esta clase. Al crear un simulacroISimpleWorkflowInstanceMerger , puedo probar que se usa de la manera que preveo, sin la necesidad de una conexión a la base de datos ni nada.
  • Uso mucho menos código, y el código es mucho más fácil de leer.
  • Si uno de los cambios de dependencia de mi dependencia, no tengo que hacer ningún cambio en el controlador. Esto es especialmente bueno cuando considera que es probable que varios controladores usen algunas de las mismas dependencias.
  • Nunca hago referencia explícita a clases de mi capa de datos. Mi aplicación web solo puede incluir una referencia al proyecto que contiene la ISimpleWorkflowInstanceMergerinterfaz. Esto me permite dividir la aplicación en módulos separados y mantener una verdadera arquitectura de varios niveles, lo que a su vez hace que las cosas sean mucho más flexibles.

Una aplicación web típica tendrá bastantes controladores. Todo el dolor de hacer DI manualmente en cada controlador realmente se acumulará a medida que su aplicación crezca. Si tiene una aplicación con solo una raíz de contexto, que nunca intenta instanciar un servicio por reflexión, entonces este no es un problema tan grande. Sin embargo, cualquier aplicación que use Inyección de dependencias será extremadamente costosa de administrar una vez que alcance un cierto tamaño, a menos que use un marco de algún tipo para administrar el gráfico de dependencia.


9

Cada vez que usa la palabra clave "nuevo", está creando una dependencia de clase concreta y una pequeña campana de alarma debería sonar en su cabeza. Se vuelve más difícil probar este objeto de forma aislada. La solución es programar en las interfaces e inyectar la dependencia para que el objeto pueda ser probado con cualquier cosa que implemente esa interfaz (por ejemplo, simulacros).

El problema es que tienes que construir objetos en alguna parte. Un patrón de Fábrica es una forma de cambiar el acoplamiento de sus POXO (Objetos simples "inserte su lenguaje OO aquí"). Si usted y sus compañeros de trabajo están escribiendo código como este, entonces un contenedor de IoC es la próxima "Mejora incremental" que puede hacer en su base de código. Cambiará todo ese desagradable código de fábrica de tus objetos limpios y lógica empresarial. Lo conseguirán y lo amarán. Diablos, dale una charla a la compañía sobre por qué lo amas y entusiasma a todos.

Si tus compañeros de trabajo aún no están haciendo DI, entonces te sugiero que te concentres en eso primero. Corre la voz sobre cómo escribir código limpio que sea fácilmente comprobable. El código DI limpio es la parte difícil, una vez que esté allí, cambiar la lógica de cableado del objeto de las clases Factory a un contenedor IoC debería ser relativamente trivial.


8

Debido a que todas las dependencias son claramente visibles, promueve la creación de componentes que están acoplados libremente y al mismo tiempo son fácilmente accesibles y reutilizables en toda la aplicación.


7

No necesita un contenedor de IoC.

Pero si sigue rigurosamente un patrón DI, encontrará que tener uno eliminará una tonelada de código redundante y aburrido.

Ese es a menudo el mejor momento para usar una biblioteca / marco, de todos modos, cuando entiendes lo que está haciendo y puedes hacerlo sin la biblioteca.


6

Simplemente estoy en el proceso de extraer el código DI interno y reemplazarlo con un COI. Probablemente eliminé más de 200 líneas de código y las reemplacé por unas 10. Sí, tuve que aprender un poco sobre cómo usar el contenedor (Winsor), pero soy un ingeniero que trabaja en tecnologías de Internet en el Siglo XXI, así que estoy acostumbrado a eso. Probablemente pasé unos 20 minutos mirando el cómo. Valió la pena mi tiempo.


3
"pero soy un ingeniero que trabaja en tecnologías de Internet en el siglo XXI, así que estoy acostumbrado a eso" -> +1
user96403

5

A medida que continúa desacoplando sus clases e invirtiendo sus dependencias, las clases siguen siendo pequeñas y el "gráfico de dependencia" continúa creciendo en tamaño. (Esto no está mal.) El uso de las características básicas de un contenedor de IoC hace que el cableado de todos estos objetos sea trivial, pero hacerlo manualmente puede ser muy pesado. Por ejemplo, ¿qué pasa si quiero crear una nueva instancia de "Foo" pero necesita una "Barra"? Y una "Barra" necesita una "A", "B" y "C". Y cada uno de ellos necesita otras 3 cosas, etc., etc. (sí, no puedo encontrar buenos nombres falsos :)).

El uso de un contenedor de IoC para construir su gráfico de objetos por usted reduce la complejidad de una tonelada y lo lleva a una configuración única. Simplemente digo "créeme un 'Foo'" y se da cuenta de lo que se necesita para construir uno.

Algunas personas usan los contenedores IoC para mucha más infraestructura, lo cual está bien para escenarios avanzados, pero en esos casos estoy de acuerdo en que puede ofuscar y dificultar la lectura del código y la depuración de nuevos desarrolladores.


1
¿Por qué seguimos apaciguando el mínimo común denominador en lugar de trabajar para levantarlos?
stevenharman

44
No dije nada sobre apaciguar. Solo dije que los escenarios avanzados pueden hacer que las cosas se ofusquen más para los nuevos desarrolladores; esto es un hecho. No dije "así que no lo hagas". Pero incluso entonces (tiempo para el abogado del diablo) a menudo hay otras formas de lograr lo mismo que hacen que una base de código sea más explícita y accesible. Ocultar la complejidad en la configuración personalizada de su herramienta de infraestructura solo porque puede no es la razón correcta, y no atrae a nadie.
Aaron Lerch

4

Ídem sobre la unidad. Si te haces demasiado grande, puedes escuchar el crujido en las vigas.

Nunca me sorprende cuando la gente comienza a hablar sobre cuán limpio se ve el código IoC, son los mismos tipos de personas que en un momento hablaron sobre cómo las plantillas en C ++ eran la forma elegante de regresar en los años 90, pero hoy en día las criticarán como arcanas . Bah!


2

En el mundo .NET, AOP no es demasiado popular, por lo que para DI un marco es su única opción real, ya sea que escriba uno usted mismo o use otro marco.

Si usó AOP, puede inyectar cuando compila su aplicación, que es más común en Java.

Hay muchos beneficios para DI, como el acoplamiento reducido para que las pruebas unitarias sean más fáciles, pero ¿cómo lo implementará? ¿Quieres usar la reflexión para hacerlo tú mismo?


El nuevo marco MEF permite justamente esto para .NET y debo decir que el código es más limpio y fácil de entender, hace que sea mucho más fácil entender el concepto DI.
terjetyl

44
@TT: MEF no es un contenedor de inyección de dependencia y no tiene nada que ver con AOP.
Mauricio Scheffer

2

Entonces, han pasado casi 3 años, ¿eh?

  1. El 50% de los que comentaron a favor de los marcos de trabajo de IoC no entienden la diferencia entre IoC y IoC Frameworks. Dudo que sepan que puedes escribir código sin ser implementado en un servidor de aplicaciones

  2. Si tomamos el marco Java Spring más popular, es la configuración de IoC movida de XMl al código y ahora se ve así

`@Configuration public class AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `Y necesitamos un marco para hacer esto ¿por qué exactamente?


1

Honestamente, no creo que haya muchos casos en los que se necesiten contenedores IoC, y la mayoría de las veces, solo agregan complejidad innecesaria.

Si lo está utilizando solo para simplificar la construcción de un objeto, tendría que preguntar, ¿está creando instancias de este objeto en más de una ubicación? ¿Un singleton no satisfaría sus necesidades? ¿Estás cambiando la configuración en tiempo de ejecución? (Cambio de tipos de fuente de datos, etc.).

En caso afirmativo, es posible que necesite un contenedor de IoC. Si no es así, simplemente está alejando la inicialización de donde el desarrollador puede verla fácilmente.

¿Quién dijo que una interfaz es mejor que la herencia de todos modos? Digamos que estás probando un servicio. ¿Por qué no usar el constructor DI y crear simulacros de las dependencias usando la herencia? La mayoría de los servicios que uso solo tienen algunas dependencias. Hacer pruebas unitarias de esta manera evita mantener un montón de interfaces inútiles y significa que no tiene que usar Resharper para encontrar rápidamente la declaración de un método.

Creo que para la mayoría de las implementaciones, decir que los Contenedores de IoC eliminan el código innecesario es un mito.

Primero, hay que configurar el contenedor en primer lugar. Entonces todavía tiene que definir cada objeto que necesita inicializarse. Por lo tanto, no guarda el código en la inicialización, lo mueve (a menos que su objeto se use más de una vez. ¿Es mejor como Singleton?). Luego, para cada objeto que haya inicializado de esta manera, debe crear y mantener una interfaz.

¿Alguno tiene alguna idea sobre esto?


Incluso para proyectos pequeños, poner la configuración en XML lo hace mucho más limpio y modular, y por primera vez hace que el código sea reutilizable (ya que ya no está contaminado con pegamento). Para productos de software adecuadas, puede configurar o permuta por ejemplo ShippingCostCalculatoro ProductUIPresentation, o cualquier otra estrategia / aplicación, dentro de exactamente la misma base de código , mediante la implementación de un solo archivo XML modificado.
Thomas W

Si se implementa willy-nilly, como práctica general, creo que agrega más complejidad, no menos. Cuando necesita intercambiar algo en tiempo de ejecución, son geniales, pero ¿por qué agregar la ofuscación? ¿Por qué mantener la sobrecarga de las interfaces adicionales para cosas que nunca se cambiarán? No digo que no use IoC para las pruebas. Por supuesto, use la propiedad o incluso la inyección de dependencia del constructor. ¿Necesitas usar una interfaz? ¿Por qué no subclasificar sus objetos de prueba? ¿No sería eso más limpio?
Fred

No necesita interfaces para hacer la configuración XML / IoC. Encontré grandes mejoras en la claridad en un proyecto pequeño con solo ~ 8 páginas y 5 o 6 servicios. Hay menos ofuscación ya que los servicios y controladores ya no necesitan código de pegamento.
Thomas W

1

Necesitaría un contenedor de IoC si necesitara centralizar la configuración de sus dependencias para que puedan intercambiarse fácilmente en masa. Esto tiene más sentido en TDD, donde se intercambian muchas dependencias, pero donde hay poca interdependencia entre las dependencias. Esto se hace a costa de ofuscar el flujo de control de la construcción de objetos hasta cierto punto, por lo que es importante tener una configuración bien organizada y razonablemente documentada. También es bueno tener una razón para hacer esto, de lo contrario, es una mera abstracción chapada en oro . Lo he visto tan mal que fue arrastrado a ser el equivalente a una declaración goto para constructores.


1

Aquí es por qué. El proyecto se llama IOC-with-Ninject. Puede descargarlo y ejecutarlo con Visual Studio. Este ejemplo usa Ninject, pero TODAS las declaraciones 'nuevas' están en una ubicación y puede cambiar por completo la forma en que se ejecuta su aplicación cambiando el módulo de enlace que debe usar. El ejemplo está configurado para que pueda vincularse a una versión simulada de los servicios o la versión real. En proyectos pequeños que pueden no importar, pero en proyectos grandes es un gran problema.

Para ser claros, ventajas como las veo: 1) TODAS las declaraciones nuevas en una ubicación en la raíz del código. 2) Totalmente re-factorizar código con un cambio. 3) Puntos extra por 'factor genial' porque es ... bueno: genial. :pags


1

Trataré de encontrar por qué mi COI podría no ser bueno desde mi perspectiva.

Como con todo lo demás, el contenedor IOC (o como lo diría Einstein I = OC ^ 2) es un concepto que debe decidir por sí mismo si lo necesita o no en su código. La reciente protesta de la moda sobre el COI es solo eso, la moda. No caigas en la moda, eso es lo primero. Hay miles de conceptos que podrías implementar en tu código. En primer lugar, estoy usando la inyección de dependencia desde que comencé a programar y aprendí el término cuando se popularizó con ese nombre. El control de la dependencia es un tema muy antiguo y se abordó hasta ahora de billones de maneras, dependiendo de lo que se estaba desacoplando de qué. Desacoplar todo de todo es una tontería. El problema con el contenedor IOC es que intenta ser tan útil como Entity Framework o NHibernate. Si bien escribir un mapeador relacional de objetos es simplemente obligatorio tan pronto como tenga que acoplar cualquier base de datos con su sistema, el contenedor IOC no siempre es necesario. Entonces, cuando el contenedor IOC es útil:

  1. Cuando tienes una situación con muchas dependencias que quieres organizar
  2. Cuando no le importa acoplar su código con un producto de terceros
  3. Cuando sus desarrolladores quieran aprender a trabajar con una nueva herramienta

1: No es tan frecuente que tenga tantas dependencias en su código, o que las conozca al principio del diseño. El pensamiento abstracto es útil cuando el pensamiento abstracto es debido.

2: acoplar su código con código de terceros es un problema enorme. Estaba trabajando con código que tiene más de 10 años y que seguía en ese momento conceptos sofisticados y avanzados ATL, COM, COM + y así sucesivamente. No hay nada que pueda hacer con ese código ahora. Lo que estoy diciendo es que un concepto avanzado ofrece una ventaja aparente, sin embargo, esto se cancela a largo plazo con la ventaja obsoleta en sí. Simplemente lo había hecho todo más caro.

3: El desarrollo de software es lo suficientemente difícil. Puede extenderlo a niveles irreconocibles si permite que algún concepto avanzado se recorte en su código. Hay un problema con IOC2. Aunque desacopla las dependencias, también desacopla el flujo lógico. Imagine que ha encontrado un error y necesita establecer un descanso para examinar la situación. IOC2, como cualquier otro concepto avanzado, lo está haciendo más difícil. Arreglar un error dentro de un concepto es más difícil que arreglar un error en un código simple, porque cuando arreglas un error, un concepto debe ser obedecido nuevamente. (Solo para darle un ejemplo, C ++ .NET cambia constantemente la sintaxis tanto que necesita pensar mucho antes de refactorizar alguna versión anterior de .NET). Entonces, ¿cuál es el problema con IOC? El problema está en resolver dependencias. La lógica para resolver está comúnmente oculta en el propio IOC2, escrito tal vez de manera poco común que necesita aprender y mantener ¿Su producto de terceros estará allí en 5 años? Microsoft no lo era.

El síndrome "Sabemos cómo" está escrito por todas partes con respecto a IOC2. Esto es similar a las pruebas de automatización. Término elegante y solución perfecta a primera vista, simplemente pone todas sus pruebas a ejecutar durante la noche y ve los resultados por la mañana. Es realmente doloroso explicar compañía tras compañía lo que realmente significan las pruebas automatizadas. Las pruebas automatizadas definitivamente no son una forma rápida de reducir la cantidad de errores que puede introducir de la noche a la mañana para aumentar la calidad de su producto. Pero, la moda está haciendo que esa noción sea molestamente dominante. IOC2 sufre el mismo síndrome. Se cree que necesita implementarlo para que su software sea bueno. Cada entrevista reciente Me preguntaron si estoy implementando IOC2 y automatización. Esa es una señal de moda: la compañía tenía una parte del código escrito en MFC que no abandonarán.

Necesita aprender IOC2 como cualquier otro concepto en software. La decisión de si es necesario utilizar IOC2 es del equipo y de la empresa. Sin embargo, al menos TODOS los argumentos anteriores deben mencionarse antes de tomar la decisión. Solo si ve que el lado positivo supera el lado negativo, puede tomar una decisión positiva.

No hay nada malo con IOC2, excepto que solo resuelve los problemas que resuelve e introduce los problemas que presenta. Nada más. Sin embargo, ir en contra de la moda es muy difícil, tienen la boca sudorosa, los seguidores de todo. Es extraño cómo ninguno de ellos está allí cuando el problema con su fantasía se hace evidente. Se han defendido muchos conceptos en la industria del software porque generan ganancias, se escriben libros, se realizan conferencias y se fabrican nuevos productos. Eso es moda, generalmente de corta duración. Tan pronto como las personas encuentran algo más, lo abandonan por completo. IOC2 es útil pero muestra los mismos signos que muchos otros conceptos desaparecidos que he visto. No sé si sobrevivirá. No hay una regla para eso. Piensas que si es útil, sobrevivirá. No, no va de esa manera. Una gran empresa rica es suficiente y el concepto puede morir en pocas semanas. Ya veremos. NHibernate sobrevivió, EF quedó en segundo lugar. Quizás IOC2 también sobrevivirá. No olvide que la mayoría de los conceptos en el desarrollo de software no tienen nada de especial, son muy lógicos, simples y obvios, y a veces es más difícil recordar la convención de nomenclatura actual que comprender el concepto en sí. ¿El conocimiento de IOC2 hace que un desarrollador sea un mejor desarrollador? No, porque si un desarrollador no pudo idear un concepto similar al IOC2, entonces le será difícil entender qué problema está resolviendo IOC2, usarlo parecerá artificial y puede comenzar a usarlo. en aras de ser una especie de políticamente correcto. No olvide que la mayoría de los conceptos en el desarrollo de software no tienen nada de especial, son muy lógicos, simples y obvios, y a veces es más difícil recordar la convención de nomenclatura actual que comprender el concepto en sí. ¿El conocimiento de IOC2 hace que un desarrollador sea un mejor desarrollador? No, porque si un desarrollador no pudo idear un concepto similar al IOC2, entonces le será difícil entender qué problema está resolviendo IOC2, usarlo parecerá artificial y puede comenzar a usarlo. en aras de ser una especie de políticamente correcto. No olvide que la mayoría de los conceptos en el desarrollo de software no tienen nada de especial, son muy lógicos, simples y obvios, y a veces es más difícil recordar la convención de nomenclatura actual que comprender el concepto en sí. ¿El conocimiento de IOC2 hace que un desarrollador sea un mejor desarrollador? No, porque si un desarrollador no pudo idear un concepto similar al IOC2, entonces le será difícil entender qué problema está resolviendo IOC2, usarlo parecerá artificial y puede comenzar a usarlo. en aras de ser una especie de políticamente correcto. ¿El conocimiento de IOC2 hace que un desarrollador sea un mejor desarrollador? No, porque si un desarrollador no pudo idear un concepto similar al IOC2, entonces le será difícil entender qué problema está resolviendo IOC2, usarlo parecerá artificial y puede comenzar a usarlo. en aras de ser una especie de políticamente correcto. ¿El conocimiento de IOC2 hace que un desarrollador sea un mejor desarrollador? No, porque si un desarrollador no pudo idear un concepto similar al IOC2, entonces le será difícil entender qué problema está resolviendo IOC2, usarlo parecerá artificial y puede comenzar a usarlo. en aras de ser una especie de políticamente correcto.


0

Personalmente, uso IoC como algún tipo de mapa de estructura de mi aplicación (Sí, también prefiero StructureMap;)). Hace que sea fácil sustituir mis implementaciones habituales de interfaz con implementaciones de Moq durante las pruebas. Crear una configuración de prueba puede ser tan fácil como hacer una nueva llamada de inicio a mi IoC-framework, sustituyendo cualquier clase que sea mi límite de prueba con un simulacro.

Probablemente esto no sea para lo que IoC está allí, pero es para lo que más lo uso.




0

Me doy cuenta de que esta es una publicación bastante antigua, pero parece estar todavía bastante activa, y pensé que contribuiría con un par de puntos que aún no se han mencionado en otras respuestas.

Diré que estoy de acuerdo con los beneficios de la inyección de dependencia, pero prefiero construir y administrar los objetos yo mismo, usando un patrón no muy diferente al descrito por Maxm007 en su respuesta. He encontrado dos problemas principales con el uso de contenedores de terceros:

1) Tener una biblioteca de terceros que administre la vida útil de sus objetos "automáticamente" puede prestarse a resultados inesperados. Hemos descubierto que, especialmente en proyectos grandes, puede tener muchas más copias de un objeto de lo que espera, y más de lo que tendría si administrara manualmente los ciclos de vida. Estoy seguro de que esto varía según el marco utilizado, pero el problema existe. Esto también puede ser problemático si su objeto contiene recursos, conexiones de datos, etc., ya que el objeto a veces puede vivir más de lo esperado. De manera inevitable, los contenedores IoC tienden a aumentar la utilización de recursos y la huella de memoria de una aplicación.

2) Los contenedores IoC, en mi opinión, son una forma de "programación de caja negra". He descubierto que, en particular, nuestros desarrolladores menos experimentados tienden a abusar de ellos. Le permite al programador no tener que pensar en cómo los objetos deberían relacionarse entre sí o cómo desacoplarlos, ya que les proporciona un mecanismo en el que simplemente pueden agarrar cualquier objeto que quieran de la nada. Por ejemplo, puede haber una buena razón de diseño para que ObjectA nunca sepa sobre ObjectB directamente, pero en lugar de crear una fábrica, un puente o un localizador de servicios, un programador inexperto simplemente dirá "no hay problema, simplemente tomaré ObjectB del contenedor de IoC". ". Esto realmente puede conducir a un mayor acoplamiento de objetos, que es lo que se supone que IoC debe ayudar a prevenir.


-8

La inyección de dependencia en un proyecto ASP.NET se puede lograr con unas pocas líneas de código. Supongo que hay una ventaja en usar un contenedor cuando tienes una aplicación que usa múltiples front-end y necesita pruebas unitarias.


1
¿Puede dar un ejemplo? ¿En qué se diferencia DI en ASP.NET en comparación con cualquier otro tipo de aplicación?
tofi9
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.