El pasado jueves 27 tuve la oportunidad de asistir a la reunión mensual del grupo de usuarios .Net de Madrid (Mad.nug) y la conversación estuvo enfocada a la arquitectura de software, inyección de dependencias, inversión de control y algo de principios SOLID, algo de lo que era bastante ajeno.
Este tema de conversación me pareció bastante interesante y he estado indagando, lo que me ha llevado a escribir a modo de artículo, algunas ideas que he adquirido de estos temas.
En este artículo se verá un resumen los principios SOLID, luego se hablará de inyección de dependencias, y finalmente un poco de Unity, un framework de inyección de dependencias creado por el equipo de Microsoft Patterns and Practices.
Principios SOLID
Son principios de diseño básicos para poder crear aplicaciones robustas y sostenibles, creados por Robert C.Martin, y es, como define Hadi Hariri en el vídeo que se enlaza al final del artículo, un acrónimo de acrónimos, es decir, una combinación de varios principios ya existentes.
- S-RP: Single Responsability Principle: Especifica que una clase (o un método) solamente tiene que tener un propósito concreto, es decir, tener una única responsabilidad o un motivo por el que cambiar. Esto se reduce en clases más pequeñas y más fáciles de entender.
- O-CP: Open Closed Principle: Se basa en delegar la responsabilidad a la clase. Si existen una serie de acciones que dependen del subtipo de una clase, es más fácil que esa funcionalidad esté implítica en la clase, y que las subclases las reimplementen (o las especifiquen) mediante polimorfismo, una de las características más comunes de los lenguajes orientados a objetos. Esto se resume en que una clase debe estar abierta a extensión y cerrada a cambios.
- L-SP: Liskov Susbtitution Principle: Explica que todas las clases que hereden de una superclase, por decirlo así, no deben replicar funcionalidad ya implementada en esta clase, con lo cual la clase base se deberá poder sustituir por las subclases en cualquier región del código.
- I-SP: Interface Segregation Principle: Propone dividir las interfaces en algo que tenga más sentido, en vez de dejar operaciones de la interfaz por implementar, en cierta manera sería una aplicación del SRP a las interfaces, teneniendo en cuenta que una clase puede implementar varias interfaces de manera simultánea. No se debe forzar a clientes a implementar métodos no necesarios.
- D-IP: Dependency Inversion Principle: Permite conseguir un desacoplamiento de las clases en el código, de tal manera que si una clase emplea otras clases, la inicialización de los objetos venga dada desde fuera. Además, si se puede sustituir las clases por interfaces, se puede extender a otro objeto que implemente la interfaz, sin variar el parámetro.
Estos cinco patrones componen SOLID, pero hay más, está la ley de Demeter, los los principios DRY… varias buenas prácticas para desarrollar buen software que van bastante más allá del tema de este artículo. Otro detalle importante es que son patrones, no metodologías, se pueden seguir o no, aunque las razones para seguirlos son bastante interesantes.
Inversión de dependencias e inyección de dependencias.
La inversión de dependencias, como se ha dicho en el apartado anterior, lleva implícita la inyección de dependencias. Para explicar este concepto, se puede mostrar el siguiente ejemplo, partiendo de este código inicial.
class MiClase { public MiClase() { ClaseA a = new ClaseA(); ClaseB b = new ClaseB(); a.UnMetodo(); b.OtroMetodo(); } }
Este código tiene un problema de acoplamiento, ya que la clase depende de otras dos clases. Si cambiase el comportamiento de las clases ClaseA o ClaseB, habría que cambiar el código de esta clase, no digamos si los constructores tienen parámetros.
Una primera mejora consistiría en pasar los objetos por argumento, de tal manera que sean definidos fuera de la clase, eliminando la dependencia en esta fase, e inyectándola mediante el constructor, por lo tanto, si el código de estos cambia, no será necesario editar esta clase, pero, nos aseguraremos que el código sigue siendo válido?:
class MiClase { public MiClase(ClaseA a, ClaseB b) { a.UnMetodo(); b.OtroMetodo(); } }
class MiClase { public MiClase(IClaseA a, IClaseB b) { a.UnMetodo(); b.OtroMetodo(); } }
Unity, un contenedor IoC
Un detalle del apartado anterior, es que el código no es del todo desacoplado, es decir, estará acoplado a la llamada al constructor o a la función, para terminar de resolver este «pequeño problema» surge Unity, que se define como un contenedor de inyección de dependencias.
El funcionamiento del contenedor permite primeramente registrar los tipos que se usarán, es decir, los objetos se devolverán al solicitar las diferentes interfaces. Una vez se han registrado, para crear los objetos solamente será necesario llamar al contenedor, y él se encargará de resolver la ruta pedida, es decir, emparejar la interfaz que se solicita con el objeto definido en la fase de registro.
Una ventaja de este contenedor es que la configuración se puede realizar vía XML, evitando el hecho de recompilar.
Resumen
Programar no es solamente escribir código, existen estilos y patrones que si los seguimos 1. No hacen daño y 2. Permiten obtener un código legible y mejorar su mantenibilidad. Espero que la lectura te haya resultado interesante, y si quieres más información, te remito a los libros y las charlas de Hadi.
Más información:
- Libro: Dependency Inyection in .NET por Mark Seemann
- Interesantísima charla de Hadi Hariri sobre el tema en el Foro de Arquitectos Microsoft 2010 y en el CodeCamp 2009, estuve presente en esta última, pero no ha sido hasta ahora que lo he podido entender con claridad.
- Ejemplo de Unity con ASP.net
Muy buen resumen Roberto, aunque como resumen, siempre se pueden dejar de lado varios aspectos, así que si me permites, me gustaría cumplimentar lo que muy bien has explicado.
El día que estuvimos comentando esto en MADNUG, hablamos de Unity como Software IoC.
Para los fan boys, decir que se eligió Unity como modelo, pero hay muchos más en el mercado, algunos de los cuales se comentó aquel día por encima (Spring, Castle Windsor, Autofac, Ninject, etc).
Aquí os dejo una pequeña lista:
http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
Adicionalmente a todo esto, se comentó que algunos de estos contenedores son algo más que contenedores. Así como Unity es independiente pero está relacionado indirectamente con Enterprise Library, Spring .NET es independiente pero forma parte más bien de un Framework, donde IoC es una parte del mismo.
De esta manera, hicimos un rápido y pequeño resumen. En MADNUG y partiendo de la experiencia de la gente se realizó las siguientes conclusiones (no toméis estas conclusiones por la línea de hecho, que cada uno haga sus pruebas):
Alguno comentó que Autofac era realmente rápido, quizás el más rápido.
Castle Windsor y Unity eran buenas alternativas muy parejas ambas. Respecto a Unity (que es de Microsoft), tenía un futuro muy prometedor ya que se estaban haciendo importantes esfuerzos, y quizás formara parte de .NET Framework en un futuro, como le pasa hoy día a MEF (¿quizás una fusión entre ambos?).
Spring .NET en cuanto a rendimiento formaba fila detrás de Castle Windsor y Microsoft Unity. Además, Spring .NET forma ahora parte de VMWare y todos sabemos que VMWare no es una ONG. Tiempo al tiempo para ver si finalmente lo hacen de pago o no.
Ninject por su parte, tenía unos varemos de rendimiento muy pobres, si bien habían prometido mejorar este aspecto en la próxima versión.
Salieron algunos detalles más, pero estos son en mi opinión los más destacables respecto a lo que se comento sobre contenedores de IoC en .NET.
Un saludo,
Jorge Serrano
«La inversión de dependencias, como se ha dicho en el apartado anterior, lleva implícita la inyección de dependencias.»
No creo que la inversión de dependencias fuerce a la inyección de las mismas. La inversión simplemente establece que entre capas se desacople usando abstracciones comunes, representadas por interfaces.
Pero dichas interfaces se pueden inyectar o, por el contrario, podrían ser instanciadas internamente por la clase que va a utilizarlas. En este caso no habría inyección. No es una buena idea por el acoplamiento que provoca pero no quita para que sea una posibilidad y, por tanto. que Dependency Inversion pueda hacerse sin necesidad de recurrir a Dependency Injection.
Muy interesante Roberto, tomo nota ;)
Saludos!
Muchas gracias Roberto excelente aporte ;)