La inyección de dependencias es una de las técnicas más útiles a la hora de desarrollar aplicaciones con bajo acoplamiento (loosely copled) en las que podemos cambiar fácilmente el comportamiento de partes de la aplicación sustituyendo unos componentes por otros.
Para aplicarla en C# tenemos dos vías fundamentales:
Inyección por constructor (constructor injection), en la que declaramos en el constructor las dependencias de una clase y dejamos que el código que instancia la clase, generalmente un contenedor IoC, pase por el construtor las dependencias que necesita la clase.
public class MyComponent { private readonly IDependency dependency; public MyComponent(IDependency dependency) { this.dependency = dependency; } }
Inyección por propiedades (property injection o setter injection), en la que declaramos propiedades del tipo de las dependencias que necesitamos y dejamos que asignen el valor a las dependencias después de construir la clase.
public class MyComponent { public IDependency Dependency { get; set; } }
La inyección por constructor es más limpia, ya que garantiza que cuando se construye el objeto cuenta con todas las dependencias que puede necesitar. La inyección por propiedades, en cambio, puede dar lugar a objetos construidos en los que no se han establecido las dependencias, provocando NullReferenceException
s al intentar acceder a ellas. Podríamos decir que usar inyección por propiedades impide garantizar el invariante de la clase y, de hecho, muchas veces es considerado un antipatrón por ese motivo.
Otra forma de verlo es que las dependencias declaradas en el constructor son dependencias requeridas por la clase y las dependencias declaradas como propiedades son dependencias opcionales sin las cuales la clase puede seguir funcionando.
En general soy muy reacio a utilizar inyección por propiedades, pero hay un caso en que me parece bastante útil y hasta recomendable: las dependencias ambientales.
Existe un tipo de dependencias que son casi globales. Puede que no para toda la aplicación, pero sí a ciertos tipos de clases, por ejemplo Controllers en una aplicación ASP.NET MVC o Presenters en una aplicación WinForms. Estas dependencias, que a falta de un nombre mejor he llamado ambientales, suelen ofrecer funcionalidades muy básicas y, en muchas ocasiones, están encapsuladas en una clase base (layer supertype) que ofrece métodos de utilidad para las clases derivadas.
Por ejemplo, en una aplicación de escritorio, casi todos los presenters van a tener una dependencia sobre IMessageBoxProvider
(o como sea que llames a la clase que permite mostrar MessageBox
es) o sobre ICurrentUserProvider
(o la clase que uses para almacenar el usuario que ha iniciado sesión en la aplicación).
En una situación como esa, tenemos dos alternativas:
- Usar inyección por constructor para todas las dependencias. Eso obliga a “manchar” los constructores de cada clase derivada con las dependencias de la clase padre.
- Usar inyección por constructor para las dependencias específicas de la clase derivada pero usar inyección por propiedad para las dependencias de la clase base.
La segunda opción permite que las clases derivadas (cada Presenter o Controller) mantengan sus constructores mucho más focalizados en las dependencias que van a necesitar para hacer su trabajo específico, en lugar de tener que declarar dependencias más ambientales que son usadas por métodos de la clase base.
El inconveniente de esto es que estamos usando inyección por propiedades para parte de las dependencias de la clase, con los problemas que ello conlleva. Es necesario recordar siempre que, además de instanciar la clase usando su constructor, debemos asignar valores a las propiedades con dependencias.
Lo bueno es que la creación de estos objetos suele quedar encapsulada en un ámbito muy pequeño de la infraestructura de la aplicación (un ControllerFactory, un ScreenActivator o similar), en la que haciendo un uso apropiado de un contenedor de IoC no tendremos problemas para construir el objeto correctamente.
Hay que tener en cuenta que seguramente también estemos creando objetos de estas clases en los tests de la aplicación, y lo más probable (y recomendable) es que no los creemos con un contenedor IoC. Para evitar que se nos olvide construir correctamente el objeto, podemos aplicar la misma técnica que con el código de producción: tener un poco de infraestructura en los test que nos ayude a construir los objetos. Podemos crear una clase base para los tests de este tipo de clases que nos ayude a inicializar correctamente sus dependencias, ya sea con mocks o con lo que más nos guste.
Conclusiones
Aunque repito que soy muy reacio a utilizar inyección por propiedades, en este caso concreto, teniendo en cuenta la alternativa, me parece una solución razonable. Además todo queda bastante acotado porque se utiliza sólo en un tipo de clases fácilmente identificable (todos los presenters, o todos los controllers, etc.). Esto permite controlar bastante bien los puntos de creación de los objetos y hacer que esta creación sea cómoda, fácil y no nos genere problemas que sólo podemos detectar en tiempo de ejecución por no haber establecido una propiedad.