dependency injection - inyeccion - ¿Por qué necesito un contenedor IoC en lugar de un código DI sencillo?
inyeccion de dependencias spring (30)
He estado usando la inyección de dependencia (DI) durante un tiempo, inyectando ya sea 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 por parte 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 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 un código que no entienden. Muchos de ellos pueden ser reacios a aprender nuevas tecnologías.
Por favor, convénceme que necesito usar un contenedor IoC. Voy a usar estos argumentos cuando hable con mis colegas desarrolladores en el trabajo.
Creo que la mayor parte del valor de una IoC se obtiene utilizando 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 varios usuarios, el contenedor de IoC puede ocuparse de algunos de los códigos de infraestructura para cargar diferentes recursos de clientes. Cuando necesite un componente que sea específico para el cliente, use un selector personalizado para manejar la lógica y no se preocupe por su código de cliente. Ciertamente, puedes construir esto tú mismo, pero aquí hay un ejemplo de cómo puede ayudar una IoC.
Con muchos puntos de extensibilidad, el IoC se puede usar para cargar componentes desde la configuración. Esto es algo común de construir, pero el contenedor proporciona herramientas.
Si desea utilizar AOP para algunas cuestiones transversales, la IoC proporciona enlaces para interceptar invocaciones de métodos. Esto es menos frecuente en los proyectos, pero IoC lo hace más fácil.
He escrito una funcionalidad como esta antes pero si necesito cualquiera de estas características ahora, preferiría usar una herramienta precompilada y probada si se ajusta a mi arquitectura.
Como lo mencionaron otros, también puede configurar de manera central las clases que desea usar. Aunque esto puede ser algo bueno, tiene un costo de desviación y complicación. 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 IoC y aprecio la funcionalidad, pero debo admitir que me he dado cuenta del inconveniente: 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).
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 del árbol de objetos, necesito tener acceso a la configuración o los 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 una sola instancia de ellas), solicite las Configuraciones o el Diccionario en el constructor y presto ... ¡aparecen mágicamente cuando las necesito!
Sin utilizar un contenedor de IoC, tendría que pasar la configuración y / o los metadatos a través de 2, 3, ..., n objetos antes de que el objeto que lo necesitaba realmente lo utilizara.
Hay muchos otros beneficios para los contenedores DI / IoC que otras personas han detallado aquí y pasar de la idea de crear objetos a solicitar objetos puede ser desconcertante, pero usar DI fue muy útil para mí y para mi equipo, así que tal vez pueda agregarlo. a tu arsenal
El uso de un contenedor consiste principalmente en cambiar de un estilo imperativo / de script de inicialización y configuración a uno declarativo . Esto puede tener algunos efectos beneficiosos diferentes:
- Reduciendo las rutinas de inicio del programa principal de hairball .
- Habilitar capacidades de reconfiguración de tiempo de implementación bastante profundas.
- Hacer del estilo inyectable de dependencia el camino de menor resistencia para nuevos trabajos.
Por supuesto, puede haber dificultades:
- El código que requiere un inicio complejo / apagado / gestión del ciclo de vida puede no adaptarse fácilmente a un contenedor.
- Probablemente tendrás que navegar por cualquier problema personal, de proceso y de cultura de equipo, pero entonces, es por eso que preguntaste ...
- Algunos de los kits de herramientas se están volviendo cada vez más pesados, alentando el tipo de dependencia profunda contra la que muchos contenedores DI comenzaron como una reacción violenta.
En mi opinión, el beneficio número uno de una IoC es la capacidad de centralizar la configuración de sus dependencias.
Si actualmente está utilizando la inyección de dependencia, su código podría tener este aspecto
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 usó una clase IoC estática, a diferencia de los archivos de configuración IMHO, los 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, tu 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.
}
}
Es de suponer que nadie lo obliga a utilizar un marco de contenedor DI. Ya estás usando DI para desacoplar tus clases y mejorar la capacidad de prueba, por lo que estás obteniendo 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árela con la curva de aprendizaje del equipo de un marco contenedor DI.
Si necesita más control sobre la administración de la vida útil de la dependencia (es decir, si siente la necesidad de implementar el patrón Singleton), consulte 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 eso 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 excelente publicación en el blog de Mark Seemann sobre esto: cuándo usar un contenedor DI
Estoy contigo, Vadim. Los contenedores IoC toman un concepto simple, elegante y útil, y conviertanlo en algo que tiene que estudiar durante dos días con un manual de 200 páginas.
Personalmente, estoy perplejo por la manera en que la comunidad de IoC tomó un hermoso y elegante artículo de Martin Fowler y lo convirtió en un montón de marcos complejos, generalmente con manuales de 200-300 páginas.
Trato de no juzgar (¡HAHA!), Pero creo que las personas que usan contenedores IoC son (A) muy inteligentes y (B) carecen de empatía por las personas que no son tan inteligentes como son. Todo tiene mucho sentido para ellos, por lo que les cuesta entender que muchos programadores comunes encontrarán confusos los conceptos. 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, 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 registró de manera agresiva e hizo muchas validaciones, que utilizó durante el desarrollo, y otra versión sin registro o validación que fue muy 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 usas contenedores IoC, tu código se vuelve, francamente, mucho más difícil de leer. La cantidad de lugares que debe mirar para averiguar qué intenta hacer el código aumenta al menos en uno. Y en algún lugar del cielo un ángel clama.
Los Contenedores IoC también son buenos para cargar dependencias de clase profundamente anidadas. 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 tenía todas estas dependencias cargadas y un 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.
}
Los marcos de IoC son excelentes si quieres ...
... tirar el tipo de seguridad. Muchos (¿todos?) Los marcos IoC te obligan a ejecutar el código si quieres estar seguro de que todo está conectado correctamente. "¡Hey! Espero haber configurado todo para que mis inicializaciones de estas 100 clases no fallen en la producción, ¡lanzando excepciones de puntero nulo!"
... ensucie su código con elementos globales (los marcos de IoC tienen que ver con cambiar estados globales).
... escriba código de mierda con dependencias poco claras que sea 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 que obviamente es defectuoso, ya que la dependencia entre Foo y Bar está cableada. Entonces se dieron cuenta de que sería mejor escribir código como
public class Foo {
public IBar Apa {get;set;}
Foo() {
Apa = IoC<IBar>();
}
}
que también es defectuoso, pero menos obvio. En Haskell, el tipo de Foo()
sería IO Foo
pero realmente no desea la IO
part y debería ser una señal de advertencia de que algo está mal con su diseño si lo obtuvo.
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 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.
También: 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 marcos de IoC sean muy inteligentes, simplemente creo que no saben matemáticas.
Me parece que ya construiste tu propio contenedor IoC (utilizando los distintos patrones que describió Martin Fowler) y te preguntas por qué la implementación de otra persona es mejor que la tuya.
Entonces, tienes un montón de código que ya funciona. Y se preguntan por qué querría reemplazarlo con la implementación de otra persona.
Pros por considerar un contenedor IoC de terceros
- Tienes errores arreglados gratis
- El diseño de la biblioteca puede ser mejor que el tuyo.
- La gente ya puede estar familiarizada con la biblioteca en particular
- La biblioteca puede ser más rápida que la tuya.
- Puede tener algunas características que desearía que implementaras pero que nunca tuviste tiempo para (¿tienes un localizador de servicios?)
Contras
- Usted consigue errores introducidos, de forma gratuita :)
- El diseño de la biblioteca puede ser peor que el tuyo.
- Tienes que aprender una nueva API
- Demasiadas características que nunca usarás
- Usualmente es más difícil depurar el código que no escribiste
- La migración de un contenedor IoC anterior puede ser tediosa
Entonces, sopese sus pros contra sus contras y tome una decisión.
Soy un adicto a la recuperación del COI. Me resulta difícil justificar el uso de IOC para DI en la mayoría de los casos en estos días. Los contenedores IOC sacrifican la comprobación del tiempo de compilación y, supuestamente, a cambio le brindan una configuración "fácil", una gestión compleja de la vida útil y el descubrimiento de dependencias sobre la marcha en tiempo real. Me parece que la pérdida de tiempo de compilación y la magia / excepciones resultantes no valen la pena en la gran mayoría de los casos. En aplicaciones empresariales grandes, pueden dificultar mucho seguir lo que está sucediendo.
No compro el argumento de la centralización porque puede centralizar la configuración estática también muy fácilmente utilizando una fábrica abstracta para su aplicación y postergando religiosamente la creación de objetos a la fábrica abstracta, es decir, realice el DI correcto.
¿Por qué no hacer DI estática sin magia de esta manera?
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 de singleton, simplemente agregue otra propiedad abstracta a la fábrica abstracta. Si necesita una dependencia transitoria, solo agregue otro método e inyecte como Func <>.
Ventajas:
- Toda la configuración y configuración de creación de objetos está centralizada.
- La configuración es solo código
- La verificación del tiempo de compilación facilita el mantenimiento, ya que no puede olvidar actualizar sus registros.
- No hay magia de reflexión en tiempo de ejecución.
Recomiendo a los escépticos que le den un próximo proyecto de campo verde y honestamente, pregúntese en qué punto necesita el contenedor. Más adelante, es fácil incluir un contenedor de IOC ya que simplemente está reemplazando una implementación de fábrica con un módulo de configuración de IOC Container.
Soy un fanático de la programación declarativa (mire cuántas preguntas de SQL respondo), pero los contenedores de IoC que he visto parecen demasiado arcanos para su propio bien.
... o tal vez los desarrolladores de contenedores IoC son incapaces de escribir documentación clara.
... o de lo contrario 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 poderosa (es decir, flexible) para ser útil en una amplia variedad de aplicaciones, a la vez que 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 bien encapsulas esa complejidad en código imperativo, o bien en código declarativo. Pero hay que representarlo en alguna parte.
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 difícil manejarlas manualmente. Incluso con las fábricas, la duplicación de su código simplemente no vale la pena.
Los contenedores IoC pueden ser complejos, sí. Pero para este caso simple que he mostrado es increíblemente fácil.
Bien, justifiquemos esto aún más. Digamos que tiene algunas entidades u objetos modelo que desea enlazar a una IU inteligente. Esta interfaz de usuario inteligente (lo llamaremos Shindows Morms) quiere que implementes INotifyPropertyChanged para que pueda realizar un seguimiento de cambios y actualizar la interfaz de usuario en consecuencia.
"Está bien, 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 termine 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;
}
Eso es un código de plomería asqueroso, y sostengo que si estás escribiendo un código como ese a mano , estás robando a tu cliente . Hay mejores, más inteligentes formas de trabajar.
¿Alguna vez escuchaste ese término, trabajas más inteligentemente, no más duro?
Bueno, imagina que un chico inteligente de tu equipo se acercó 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 tejer ese comportamiento de propiedades 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 IoC que estés usando, podrías 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 propiedades virtuales del objeto en cuestión.
¿Esto es magia? Si Si puede confiar en el hecho de que este código hace su trabajo, entonces puede omitir de forma segura toda esa propiedad que encierra mumbo-jumbo. Tienes problemas de negocios para resolver.
Algunos otros usos interesantes de una herramienta IoC para hacer AOP:
- Transacciones declarativas y anidadas de base de datos
- Unidad de trabajo declarativa y anidada
- Explotación florestal
- Pre / Post condiciones (Diseño por Contrato)
Here es por qué. El proyecto se llama IOC-with-Ninject. Puedes descargarlo y ejecutarlo con Visual Studio. Este ejemplo utiliza Ninject pero TODAS las declaraciones ''nuevas'' están en una ubicación y puede cambiar completamente cómo se ejecuta su aplicación cambiando qué módulo de enlace usar. El ejemplo está configurado para que pueda enlazar a una versión simulada de los servicios o la versión real. En pequeños proyectos puede que no importe, pero en grandes proyectos es un gran problema.
Solo para ser claros, las ventajas tal como las veo: 1) TODAS las declaraciones nuevas en una ubicación en la raíz del código. 2) Re-factorizar totalmente el código con un cambio. 3) Puntos extra por ''factor cool'' porque es ... bueno: cool. :pag
Debido a que todas las dependencias son claramente visibles, promueve la creación de componentes que se acoplan de forma flexible y, al mismo tiempo, son fácilmente accesibles y reutilizables en toda la aplicación.
EL CONTENEDOR DEL COI RESUELVE UN PROBLEMA QUE PODRÍA NO TENER PERO ES UN PROBLEMA AGRADABLE QUE TENER
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 lo escriba usted mismo o use otro marco.
Si usó AOP, puede inyectar cuando compila su aplicación, lo cual es más común en Java.
La ID tiene muchos beneficios, como el acoplamiento reducido, por lo que las pruebas unitarias son más fáciles, pero ¿cómo lo implementará? ¿Quieres utilizar la reflexión para hacerlo tú mismo?
Entonces, han pasado casi 3 años, ¿eh?
El 50% de los que comentaron a favor de los marcos IoC no entienden la diferencia entre los marcos IoC y IoC. Dudo que sepan que puede escribir código sin ser implementado en un servidor de aplicaciones
Si tomamos el framework Java Spring más popular, es la configuración 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?
Me doy cuenta de que este es un post bastante antiguo, pero parece que todavía es razonablemente activo, y pensé que aportaría 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, utilizando un patrón que no sea 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 dar resultados inesperados. Hemos encontrado que, especialmente en proyectos grandes, puede tener muchas más copias de un objeto de lo que espera, y más de las que tendría si estuviera administrando manualmente los ciclos de vida. Estoy seguro de que esto varía según el marco utilizado, pero el problema existe, no obstante. 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. Por lo tanto, inevitablemente, 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 encontrado que, en particular, nuestros desarrolladores menos experimentados tienden a abusar de ellos. Le permite al programador no tener que pensar en cómo deben relacionarse los objetos entre sí o cómo desacoplarlos, porque 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 directamente acerca de ObjectB, sino que en lugar de crear una fábrica o un puente o un localizador de servicios, un programador inexperto simplemente dirá "no hay problema, simplemente agarraré ObjectB del contenedor IoC". ". Esto realmente puede llevar a un aumento en el acoplamiento de objetos, que es lo que se supone que IoC ayuda a prevenir.
Sinceramente, no creo que haya muchos casos en los que se necesiten contenedores IoC, y la mayoría de las veces, solo agregan una complejidad innecesaria.
Si lo está usando solo para simplificar la construcción de un objeto, tendría que preguntar, ¿está creando una instancia 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).
Si es así, entonces es posible que necesite un contenedor IoC. Si no es así, entonces solo estás 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 mediante herencia? La mayoría de los servicios que uso solo tienen algunas dependencias. Hacer la prueba unitaria 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 IoC eliminan el código innecesario es un mito.
Primero, está configurando el contenedor en primer lugar. Entonces todavía tiene que definir cada objeto que necesita inicializarse. Así que 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?
A medida que continúa desacoplando sus clases e invirtiendo sus dependencias, las clases continúan siendo pequeñas y el "gráfico de dependencia" continúa creciendo en tamaño. (Esto no es malo.) 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 de forma manual puede ser muy engorroso. 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 3 otras cosas, etc. etc. (sí, no puedo encontrar buenos nombres falsos :)).
El uso de un contenedor IoC para construir su gráfico de objetos por usted reduce la complejidad de una tonelada y lo empuja hacia fuera en la configuración de una sola vez. 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 que está bien para escenarios avanzados, pero en esos casos estoy de acuerdo en que puede ofuscar y hacer que el código sea difícil de leer y depurar para los nuevos desarrolladores.
Cada vez que utiliza la palabra clave "nueva", está creando una dependencia de clase concreta y una pequeña campana de alarma debe sonar en su cabeza. Se vuelve más difícil probar este objeto de forma aislada. La solución es programar las interfaces e inyectar la dependencia para que el objeto pueda probarse con cualquier cosa que implemente esa interfaz (por ejemplo, simulacros).
El problema es que tienes que construir objetos en algún lugar. Un patrón de fábrica es una forma de cambiar el acoplamiento de sus POXOs (Plain Old "inserte su lenguaje OO aquí" Objetos). Si usted y sus compañeros de trabajo escriben un código como este, entonces un contenedor IoC es la siguiente "Mejora incremental" que puede realizar en su base de código. Cambiará todo ese desagradable código de fábrica de sus objetos limpios y su lógica empresarial. Lo conseguirán y lo amarán. Heck, da una charla de la compañía sobre por qué te encanta y entusiasma a todos.
Si sus compañeros de trabajo todavía no están haciendo DI, entonces le sugiero que se centre en eso primero. Corre la voz sobre cómo escribir código limpio que sea fácilmente comprobable. Limpiar el código DI es la parte difícil; una vez que esté allí, cambiar la lógica de cableado del objeto de las clases de fábrica a un contenedor IoC debería ser relativamente trivial.
Descubrí que la implementación correcta de la inyección de dependencia 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, flexibilidad, capacidad de mantenimiento y escalabilidad del código: prácticas como el principio de responsabilidad única, separaciones de inquietudes y codificación contra las APIs. Se siente como si me estuvieran obligando a escribir clases y métodos más modulares y de tamaño reducido, lo que hace que el código sea más fácil de leer, ya que se puede tomar en fragmentos de tamaño reducido.
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 kernel y cargar 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, habría sido más rápido usar el marco IoC, ya que los módulos definen prácticamente todo esto por convención.
Después de haber dedicado un tiempo a estudiar las respuestas y los comentarios sobre esta pregunta, estoy convencido de que las personas que se oponen al uso de un contenedor IoC no están practicando la inyección de dependencia verdadera. 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 se usa DI manualmente como cuando se usa un contenedor IoC. La diferencia debe residir completamente en unos pocos "puntos de lanzamiento" dentro de la aplicación.
En otras palabras, si no te gustan los contenedores 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 usas la reflexión en cualquier lugar. Si bien odio lo que hace la reflexión en el código de navegación, debes reconocer que hay ciertas áreas en las que realmente no se puede evitar. ASP.NET MVC, por ejemplo, intenta crear una instancia del controlador a través de 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", como:
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 de unidad de esta clase. Al crear un simulacro
ISimpleWorkflowInstanceMerger
, puedo probar que se usa de la manera que anticipo, sin la necesidad de una conexión de base de datos ni nada. - Utilizo 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 si se considera que es probable que varios controladores usen algunas de las mismas dependencias.
- Nunca menciono explícitamente las clases de mi capa de datos. Mi aplicación web solo puede incluir una referencia al proyecto que contiene la
ISimpleWorkflowInstanceMerger
interfaz. Esto me permite dividir la aplicación en módulos separados y mantener una verdadera arquitectura de múltiples niveles, 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 a mano en cada controlador realmente se sumará a medida que su aplicación crezca. Si tiene una aplicación con una sola raíz de contexto, que nunca intenta crear una instancia de un servicio por reflexión, entonces este no es un problema tan grande. Sin embargo, cualquier aplicación que utilice la inyección de dependencia 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.
Dittos sobre la unidad. Hazte demasiado grande, y puedes escuchar el crujido de las vigas.
Nunca me sorprende cuando la gente empieza a decir que el código IoC limpio es el mismo tipo de gente que alguna vez habló sobre cómo las plantillas en C ++ eran la manera elegante de volver en los años 90, sin embargo, hoy en día las criticarán como arcanas. . Bah!
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 de usar un contenedor cuando tienes una aplicación que usa varios front-end y necesita pruebas unitarias.
Necesitaría un contenedor IoC si tuviera que centralizar la configuración de sus dependencias para que puedan ser fácilmente intercambiadas 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 del objeto 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 simple extracción de oro . Lo he visto tan mal que fue arrastrado hasta ser el equivalente a una instrucción goto para los constructores.
No necesitas un contenedor IoC.
Pero si está siguiendo rigurosamente un patrón DI, encontrará que tener uno eliminará una tonelada de código redundante y aburrido.
De todos modos, ese es a menudo el mejor momento para usar una biblioteca / marco: cuando entiendes lo que está haciendo y podrías hacerlo sin la biblioteca.
No necesita un marco para lograr la inyección de dependencia. Puedes hacer esto por conceptos básicos de Java también. http://en.wikipedia.org/wiki/Dependency_injection#Code_illustration_using_Java
Personalmente, uso IoC como una especie de mapa de estructura de mi aplicación (Sí, también prefiero StructureMap;)). Facilita la sustitución de las implementaciones de mi interfaz de usuario con las 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 la clase que sea mi límite de prueba con un simulacro.
Probablemente esto no sea para lo que está IoC, pero es para lo que más lo uso.
Resulta que estoy en el proceso de extraer el código DI de cosecha propia y reemplazarlo con un COI. Probablemente eliminé más de 200 líneas de código y lo reemplacé con aproximadamente 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 21 así que estoy acostumbrado a eso. Probablemente pasé unos 20 minutos revisando los consejos. Esto bien valió mi tiempo.
Trataré de descubrir por qué la COI podría no ser buena desde mi perspectiva.
Al igual que con todo lo demás, el contenedor IOC (o como Einstein lo pondría I = OC ^ 2) es un concepto que debe decidir si lo necesita o no en su código. La reciente indignación 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 el momento en billones de formas, dependiendo de lo que se estaba desvinculando 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.Mientras que escribir un mapeador objeto-relacional es simplemente una obligación tan pronto como tenga que acoplar cualquier base de datos con su sistema, el contenedor IOC no siempre es necesario. Así que cuando el contenedor IOC es útil:
- Cuando tienes una situación con muchas dependencias quieres organizar.
- Cuando no le importa acoplar su código con un producto de terceros
- 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 tenga conocimiento de ellas al principio del diseño. El pensamiento abstracto es útil cuando el pensamiento abstracto es debido.
2: Acoplar su código con un código de terceros es un problema de HuGe. Estaba trabajando con un código que tiene más de 10 años y que estaba siguiendo en ese momento conceptos sofisticados y avanzados ATL, COM, COM +, etc. No hay nada que puedas hacer con ese código ahora. Lo que estoy diciendo es que un concepto avanzado ofrece una ventaja aparente, pero esto se cancela a largo plazo con la ventaja obsoleta. Solo había hecho todo más caro.
3: El desarrollo de software es bastante 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 es desacoplamiento de dependencias, también es desacoplamiento del flujo lógico. Imagina que has encontrado un error y necesitas establecer un descanso para examinar la situación. IOC2, como cualquier otro concepto avanzado, lo hace más difícil. Reparar un error dentro de un concepto es más difícil que corregir un error en un código simple, porque cuando se soluciona un error, un concepto debe obedecerse nuevamente. (Solo para darle un ejemplo, C ++ .NET cambia constantemente la sintaxis tanto que debe pensar mucho antes de refactorizar una versión anterior de .NET). ¿Cuál es el problema con IOC? El problema está en resolver dependencias. La lógica para resolver está comúnmente oculta en el mismo IOC2,escrito tal vez de una 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 todo el lugar con respecto a IOC2. Esto es similar a las pruebas de automatización. Fancy term y solución perfecta a primera vista, simplemente pones todas tus pruebas para ejecutar durante la noche y ver los resultados en la mañana. Es realmente doloroso explicar empresa tras empresa lo que realmente significa la prueba automatizada. La prueba automatizada definitivamente no es 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 esa noción molesto dominante. IOC2 sufre el mismo síndrome. Se cree que necesita implementarlo para que su software sea bueno. Entrevista reciente de EvErY Me preguntaron si estoy implementando IOC2 y la automatización. Ese es un signo de moda: la compañía tenía una parte del código escrito en MFC que no abandonarán.
Necesitas aprender IOC2 como cualquier otro concepto en software. La decisión de 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 es mayor que el lado negativo, puede tomar una decisión positiva.
No hay nada malo con IOC2, excepto que resuelve solo 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 sudor en la boca, los seguidores de cualquier cosa. Es extraño que 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 llevan a cabo conferencias y se hacen 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 va a sobrevivir. No hay ninguna 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. Tal vez IOC2 sobrevivirá también.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 algunas veces es más difícil recordar la convención de nombres actual que entender 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 de IOC2, será difícil para él o ella entender qué problema está resolviendo IOC2, usarlo parecerá artificial y él o ella pueden comenzar a usarlo por el bien de ser algún tipo 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 de IOC2, será difícil para él o ella entender qué problema está resolviendo IOC2, usarlo parecerá artificial y él o ella pueden comenzar a usarlo por el bien de ser algún tipo 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 de IOC2, será difícil para él o ella entender qué problema está resolviendo IOC2, usarlo parecerá artificial y él o ella pueden comenzar a usarlo por el bien de ser algún tipo de políticamente correcto.