Posts tagged solid
Introducción con ejemplos a Castle Windsor, un contenedor de inversión de control para .NET (parte 1)
6 Jan 11
- Introducción con ejemplos a Castle Windsor (parte 2)
- Introducción con ejemplos a Castle Windsor (parte 3)
Castle Windsor nos brinda una multitud de interesantes características. Para hacer una introducción bastante completa y digerible, la he dividido en 3 partes. En la última parte dejaré descargable el código fuente de todos los ejemplos. Comenzemos!
Puedes descargarte el código fuente de los ejemplos: Rbp.Spike_.CastleWindsor.zip (1.24MB)
Desde hace un tiempo, intento aplicar a todos mis desarrollos los fantásticos principios SOLID. Son conjunto de principios de diseño orientado a objetos, recopilados por Uncle Bob Martin (@unclebobmartin), que hablan en términos de la gestión de dependencias. Si no los conoces aún, te recomiendo encarecidamente que leas sobre ellos.
¿Qué es la inversión de dependencias?
Uno de cinco principios SOLID es el llamado The Dependency Inversion Principle (DIP). El principio dice lo siguiente:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
Coloquialmente podríamos decir que un módulo A no debe depender directamente de un módulo B, sino de la abstracción de B (una interfaz o clase que sirva de base para un conjunto de clases hijas).
Veamos un ejemplo sencillo, imaginémonos que tenemos una clase Cliente con un método Serializar que devuelve un string con el objeto serializado:
public class Cliente
{
public string Serializar()
{
SerializadorEnXml serializador = new SerializadorEnXml();
return serializador.Serializar(this);
}
}
public class SerializadorEnXml
{
public string Serializar(Cliente cliente)
{
// Código para serializar el cliente
}
}
La clase Cliente tiene una dependencia directa de la clase SerializadorEnXml. Si quisieramos hacer un test unitario del método Serializar de la clase Cliente, no podríamos simular el comportamiento del SerializadorEnXml (hacer su correspondiente Stub o Mock). Por otra parte, si más adelante quisieramos serializar en Json en vez de en Xml, deberíamos tocar el código de la clase cliente.
La forma de solucionar esto es invertir la dependencia haciendo que la clase Cliente no dependa directamente de la clase SerializdorEnXml, sino de una abstacción de la misma. Veamos como quedaría:
public class Cliente
{
private ISerializador _serializador;
public Cliente(ISerializador serializador)
{
_serializador = serializador;
}
public string Serializar()
{
return _serializador.Serializar(this);
}
}
public interface ISerializador
{
string Serializar(Cliente cliente);
}
public class SerializadorEnXml : ISerializador
{
public string Serializar(Cliente cliente)
{
// Código para serializar el cliente
}
}
Ahora, la clase Cliente no depende de ninguna clase de serialización, sino de una interfaz. El encargado de decidir qué clase será quien se encargue de serializar el cliente no será el propio Cliente, sino el responsable de crear un objeto de Cliente, por ejemplo, una factoría.
En nuestro caso, vamos a ver cómo solucionar este problema con el framework Castle Windsor.
¿Qué es Castle Windsor?
Sin entrar en mucho detalle, podríamos decir que:
- Es un framework que nos implementa la funcionalidad necesaria para conseguir la inversión de control en nuestros desarrollos.
- Es sumamente extensible y completo como iremos viendo. Si no hace lo que necesitas, seguramente podrás extenderlo e implementártelo tú mismo.
- Soporta configurarlo a través de ficheros XML o mediante una fantástica API fluida. Incluso soporta configuración por convención.
- Está preparado para ser usado en entornos web, cliente pesado, silverlight, etc.
- Soporta, de fábrica, la integración con múltitud de frameworks de terceros como son NHibernate, WCF, log4net, NLog, ActiveRecord, MonoRail, ASP.NET MVC, Rhino Service Bus, WCF, Quartz.Net, Rhino Security, SolrSharp, y algunas más.
Existen alternativas a este framework, como son Unity 2 (parte de Microsoft Enterprise Library), StructureMap y Spring.NET.
Conceptos básicos de Castle Windsor
Castle Windsor es muy sencillo de comenzar a usar, y por otra parte, cubre prácticamente todas las necesidades de inversión de control que podamos necesitar. Y si no la cubre, podremos extender el framework sin problemas.
Antes de comenzar, aclaremos unos conceptos básicos manejados por Castle Windsor:

Servicio
Es la definición de un contrato que define una unidad de funcionalidad que posteriomente deberá ser implementada por uno o varios componentes.
Componente
Es la implementación de nuestro servicio que será instanciado y gestionado por nuestro contenedor.
Dependencias
Son aquellos otros servicios que necesita un componente para funcionar.
En el ejemplo anterior, el Servicio sería la interfaz ISerializador, el componente sería clase SerializadorEnXml y no tendría dependencias.
Un ejemplo de uso
Las librerías de Castle Windsor, puedes descargártelas desde la página de Castle Project.
Para comenzar a usarlo, sólo necesitaremos hacer referencia en nuestro proyecto de las librerías Castle.Core.dll y Castle.Windsor.dll.
El ejemplo anterior, usando Castle Windsor para la inversión de control, quedaría implementado de la siguiente manera:
IWindsorContainer container = new WindsorContainer(); container.Register(Component.For<ISerializador>().ImplementedBy<SerializadorEnXml>()); Cliente cliente = new Cliente(container.Resolve<ISerializador>()); cliente.Serializar();
Responsabilidad de cada línea:
- Instanciamos nuestro contenedor. El contenedor es el encargado de resolver, crear y liberar los componentes.
- Registramos nuestro servicio definiendo qué componente lo implementará.
- Creamos nuestro cliente usando nuestro contenedor para resolver la implementación de nuestro servicio (su colaborador).
- Usamos el cliente obtenido.
Registrar los componentes
La primera tarea que debemos realizar con Windsor es la de registrar nuestros componentes para que luego puedan ser resueltos por el contenedor. En nuestra aplicación, podemos tener tantos contenedores diferentes como necesitemos. Windsor nos permite definir la configuración mediante ficheros XML o mediante código con su API fluída (Fluent API). En los ejemplos sólo usaré la configuración por código. Veamos un ejemplo mediante un test implementado con xUnit:
[Fact]
public void Registrar_componentes_uno_a_uno()
{
_container.Register(
Component.For<IRepository>().ImplementedBy<ClientRepository>(),
Component.For<IDataStore>().ImplementedBy<Database>());
}
Hemos registrado dos componentes. Uno para el servicio IRepository mediante la clase ClientRepository y otro para el servicio IDataStore implementado por la clase Database.
Mediante la configuración por código también podemos registrar los componentes por convención, en vez de uno a uno:
[Fact]
public void Registrar_componentes_por_convencion_y_configurarlos_como_transient()
{
_container.Register(
AllTypes.FromThisAssembly()
.Where(Component.IsInNamespace("Rbp.Spike.CastleWindsor.LogicLayer"))
.WithService.AllInterfaces()
.Configure(component => component.LifeStyle.Transient));
_container.Resolve<IRepository>();
_container.Resolve<IDataStore>();
}
Este test registra todos los componentes del ensamblado actual que se encuentre en el namespace “Rbp.Spike.CastleWindsor.LogicLayer” y finalmente define el LifeStyle al tipo Transient. En el siguiente punto explicaré qué son los LifeStyle.
Si un componente en concreto tuviera una configuración extra especial, podemos definírsela a partir del método ConfigureFor<>:
private void RegistrarContenedorPorConvencion()
{
_container.Register(
AllTypes.FromThisAssembly()
.Where(Component.IsInNamespace("Rbp.Spike.CastleWindsor.LogicLayer"))
.WithService.AllInterfaces()
.ConfigureFor<ExtendedDao>(x => x.Named("extendido"))
.ConfigureFor<IRepository>(
x => x.ServiceOverrides(
ServiceOverride.ForKey<IDao>().Eq("extendido"))));
}
En este ejemplo, al componente ExtendedDao se le ha dado un nombre específico (más adelante veremos para qué sirve) y a los componentes del servicio IRepository se les redefine, mediante ServiceOverrides, la dependencia (o colaborador) a usar (también lo veremos más adelante).
Windsor trae de fábrica un amplio abanico de posibilidades para definir qué componentes registrar por convención. En la documentación oficial encontrarás explicadas todas las posibilidades.
Por defecto, Singleton
Cada vez que resolvemos el mismo servicio del contenedor, ¿nos creará una instancia nueva del componente? ¿o nos devolverá siempre la misma instancia?. Depende, de la configuración definida al componente a la hora de registrarlo.
Este concepto es llamado por el framework LifeStyle. A la hora de registrar un objeto, por defecto, lo hará definiéndole el LifeStyle Singleton. Es decir, la primera vez que resolvamos un servicio, generará un objeto del componente que implementa el servicio. Las próximas veces que resolvamos el mismo servicio, nos devolverá la misma referencia al objeto del componente que se creó inicialmente.
Castle Windsor soporta los siguientes LifeStyles: Singleton (por defecto), Transient, PerThread, PerWebRequest, Pooled y Custom. Custom nos permite implementar un ciclo de vida propio.
Comprobemos esto con uno par de test unitarios:
[Fact]
public void Lifestyle_singleton_por_defecto()
{
_container.Register(
AllTypes.FromThisAssembly()
.Where(Component.IsInNamespace("Rbp.Spike.CastleWindsor.LogicLayer"))
.WithService.AllInterfaces());
IRepository repositorio1 = _container.Resolve<IRepository>();
IRepository repositorio2 = _container.Resolve<IRepository>();
Assert.Equal(repositorio1, repositorio2);
}
Podemos observar que las dos variables (repositorio1 y repositorio2) tienen la referencia al mismo objeto.
Si hubiéramos configurado los componentes con LifeStyle = Transient, en cada resolución, el contenedor hubiera creado instancias nuevas del componente y las variables tendrían referencias a objetos distintos.
Si alguna vez usas el LifeStyle Transient, recuerda liberar los componentes del contenedor cuando ya no los vayas a usar. Esto es necesario porque el contenedor guardar un registro de los objetos creados. Para liberarlos, sólo tienes que usar el método Release del contenedor. De otra forma, tu desarrollo usará más y más memoria del sistema a lo largo del tiempo, hasta que el sistema deje de funcionar correctamente.
Continúa con la 2ª parte de la introducción a Castle Windsor