La verdad, no soy consciente de cuántos son los desarrolladores que conocen el significado de Inyección de Dependencias (Dependency Injection) o Inversión de control (Inversion of Control). Para ser sincera, nunca trabajé con ello en ningún proyecto real pero si he intentado recopilar información y conceptos para poder aplicarlos a mis proyectos personales.
Generalmente, cuando tenemos una clase que depende de otras para ciertas acciones, necesitamos inicializar instancias de las mismas para poder utilizarlas posteriormente. En ocasiones, una de las alternativas puede ser crear un objeto de dichas clases de forma privada e inicializarlas, utilizando el constructor de la clase principal.
Si vemos estas acciones desde el punto de vista de la Inyección de Dependencias y la Inversión de Control, no sería la forma más óptima debido a que la clase que sufre estas dependencias no debería ser la responsable de la creación de las mismas.
¿QUÉ CONSEGUIMOS?
- Desacoplamiento.
- Mejora la posibilidad de testeo de la aplicación.
- Mejor mantenimiento a la hora de realizar cambios de los componentes, gracias a la modularidad.
- […]
Ambos conceptos están tan ligados que, en ocasiones, no se hace distinción. Se utiliza el concepto Inversión de Control para delegar en otro componente, un framework por ejemplo, la responsabilidad de crear las instancias necesarias en lugar de crearlas nosotros mismos. Por otro lado, la Inyección de Dependencias es el término utilizado cuando una clase depende de otra y, a través del constructor generalmente acepta un parámetro del tipo del cual depende.
Para llevar a cabo el patrón de diseño de Inyección de Dependencias, es necesario el uso de interfaces y, lo más óptimo sería utilizar alguno de los frameworks disponibles para llevar a cabo la Inversión de Control. Algunos de estos frameworks son: Spring.NET, Windsor Container, StructureMap, Unity, etcétera.
EJEMPLO A EVITAR
Por ver un ejemplo, supongamos que tenemos el siguiente código:
using System.Web.Mvc;
using IoC.Models;
namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController()
{
_twitterService = new TwitterService();
}
public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}
Tenemos un controlador, en una aplicación ASP.NET MVC, donde estamos haciendo uso de una librería que conecta con Twitter. Cuando se solicita la acción Index de este controlador, el controlador se crea, a través del constructor inicializa la variable _twitterService y realiza la llamada a FetchTweets. Esto funciona sin problemas, pero supone un inconveniente a la hora de realizar pruebas unitarias.
Por otro lado, si el día de mañana queremos, por ejemplo, utilizar otra clase que implemente ITwitterService bien porque hemos cambiado de librería, porque la forma de trabajar con Twitter es totalmente distinta, etcétera, deberíamos modificar a su vez este controlador(es) para modificar en el constructor la clase que implementa la interfaz a partir de ahora. Este es un caso bien simple pero ¿Y si nuestros controladores son dependientes de más de una clase y las mismas están en constante revisión, actualización, modificación, etcétera? La solución es bien simple:
using System.Web.Mvc;
using IoC.Models;
namespace IoC.Controllers
{
[HandleError]
public class HomeController : Controller
{
private readonly ITwitterService _twitterService;
public HomeController(ITwitterService twitterService)
{
_twitterService = twitterService;
}
public ActionResult Index()
{
return View(_twitterService.FetchTweets());
}
}
}
Gracias a la inyección de dependencias, liberamos al controlador de la carga de generar las instancias que necesita, lo abstraemos del tipo de clase que implementa la interfaz en este momento y conseguimos modularizar la aplicación.
PRUEBAS UNITARIAS
Si quisiéramos hacer un test, por ejemplo, que comprobara que al llamar a la acción Index el método FetchTweets es llamado, sin realizar la llamada real a Twitter e incluso sin hacer uso de la conexión que esto requiere a Internet, podríamos hacerlo de la siguiente manera:
using IoC.Controllers;
using IoC.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
namespace IoC.Tests.Controllers
{
[TestClass]
public class HomeControllerTest
{
[TestMethod]
public void HomeController_AlLlamarALaAccionIndex_FetchTweetsEsLlamado()
{
//Arrange
var mockTwitterService = MockRepository.GenerateMock<TwitterService>();
mockTwitterService.Stub(m => m.FetchTweets()).Return(null);
var homeController = new HomeController(mockTwitterService);
//Act
homeController.Index();
//Assert
mockTwitterService.AssertWasCalled(m => m.FetchTweets());
}
}
}
Nota: En este ejemplo he utilizado la librería RhinoMocks para crear la prueba unitaria.
Esto es realmente importante si queremos hacer pruebas unitarias de la aplicación, pero también es cierto que se nos presenta el siguiente problema ¿Cada vez que llame a HomeController voy a tener que encargarme y asegurarme en cada caso de que reciba una instancia de las interfaces que solicita el constructor? Aquí es donde entra en juego IoC y los numerosos frameworks existentes para este rol.
Para ver un pequeño ejemplo de cómo podríamos delegar esta funcionalidad en uno de los frameworks que soportan Inversión del Control, voy a utilizar StructureMap como demostración. Para descargar la última versión del framework podemos dirigirnos a este enlace.
CONFIGURANDO STRUCTUREMAP
En este ejemplo, vamos a configurar StructureMap de tal forma que sepa cómo actuar en el caso de requerir una instancia para una interfaz de tipo ITwitterService. Para ello, me he creado la siguiente clase:
using IoC.Models;
using StructureMap;
namespace IoC.StructureMapConfiguration
{
public static class BootStrapper
{
public static void SetupContainer()
{
ObjectFactory.Configure(s => s.For<ITwitterService>().Use<TwitterService>());
}
}
}
En una sola línea, le estoy indicando que para la interfaz ITwitterService, debemos usar una instancia de la clase TwitterService. La magia de todo esto es que, si el día de mañana esta interfaz es implementada por otra clase, y además esta interfaz es usada en numerosos sitios de nuestra aplicación, solamente debemos modificar esta línea para que la clase que la implementa comience a servirse como dependencia en los casos que lo requiera.
CONTROLADORES ASP.NET MVC YSTRUCTUREMAP
Por otro lado, si lo que queremos es trabajar con ASP.NET MVC, debemos realizar una serie de cambios: Cuando nosotros hacemos una petición en una aplicación con ASP.NET MVC, la clase ControllerBuilder genera de forma automática el controlador solicitado, se despacha la petición y el controlador finaliza. Para poder utilizar las propiedades de StructureMap, necesitamos crear una clase que herede de la factoría de controladores. De esta manera, controlaremos el momento en el cual se solicita una instancia de un controlador y, si este tiene dependencias, poder administrarlas con la configuración realizada anteriormente para StructureMap en la clase BootStraper.
using System;
using System.Web.Mvc;
using System.Web.Routing;
using StructureMap;
namespace IoC.StructureMapConfiguration
{
internal class StructureControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
return ObjectFactory.GetInstance(controllerType) as IController;
return base.GetControllerInstance(requestContext, controllerType);
}
}
}
INICIALIZANDO LA CONFIGURACIÓN DE STRUCTUREMAP Y ASOCIADO LA NUEVA FACTORÍA DE CONTROLADORES
Para finalizar, necesitamos inicializar tanto la configuración creada en BootStraper, para que StructureMap reconozca la interfaz especificada, como la asignación de la nueva factoría de controladores a la aplicación ASP.NET MVC. La mejor ubicación para este caso concreto, podría ser el archivo Global.asax.
using System.Web.Mvc;
using System.Web.Routing;
using IoC.StructureMapConfiguration;
namespace IoC
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
BootStrapper.SetupContainer();
ControllerBuilder.Current.SetControllerFactory(new StructureControllerFactory());
}
}
}
Si arrancamos la aplicación, comprobaríamos que efectivamente se crea un controlador con su dependencia y delegamos esta acción gracias a la Inversión de Control.
Adjunto el proyecto por si fuera de utilidad.
¡Saludos!
Gracias a Hadi Hariri por sus enseñanzas 🙂