1/12/2006 7:17
Miguel Jimenez
Provider Pattern: Guia práctica para desacoplar aplicacones .NET 2.0
En el siguiente post voy a explicar un patrón muy usado en el desarrollo de software y como encaja en el ecosistema del .NET Framework. Este patrón puede ser catalogado, probablemente, en la categoría de estructurales y se usa principalmente para desacoplar componentes de forma que abstracciones e implementaciones puedan variar independientemente.
Usar el patrón proveedor en .NET (y en la mayoría de lenguajes orientados a objetos) es muy sencillo. Necesitamos los siguientes elementos:
- Una definición del proveedor, usada como un contrato que especifíca que se puede hacer
- Una o varias implementaciones del proveedor declarado
- Un consumidor que desea hacer uso del proveedor
Imaginad el siguiente escenario: Existe una definición básica de lo que debe hacer un coche (definición de proveedor) y lo que es considerado como tal: tiene que tener 4 ruedas, un volante, etc... en el mercado, es posible encontrar varias implementaciones que concuerdan con la definición de coche (implementaciones del proveedor), y nosotros, como consumidores, podemos usar la definición del proveedor (coche) para cambiar la implementación sin afectar al comportamiento (somos capaces de conducir cualquier coche, de cualquier marca, porque todos usan la misma definción de contrato, el mismo interfaz: volante, acelerador, ruedas, etc...).
Ahora imaginemoslo en el contexto del desarrollo de software. Tenemos un interfaz que define un conjunto de propiedades y métodos que nuestros proveedores deben implementar; después tenemos una o varias implementaciones de ese interfaz; y, por supuesto, una aplicación o servicio que los consume puede usar el interfaz para seleccionar la implementación a usar sin que afecte a su comportamiento. Echad un vistazo al siguiente diagrama que explica como funciona este patrón:

Es bastante impresionante, práctico y sencillo cuando desseamos crear aplicaciones desacopladas y componentizadas. Simplemente usamos interfaces. Orientación a objetos. Este patrón se ha usado muchisimo en la implementación de ASP.NET 2.0, sobre todo en la creación de los servicios de aplicación. Por ejemplo, el MembershipProvider (contrato o definíción del proveedor) puede ser bien un SqlMembershipProvider o un proveedor propio OracleMembershipProvider (implementaciones concretas del proveedor), ambos proveen la misma funcionalidad (especificada en el contrato del proveedor) pero con diferente comportamiento (uno utiliza SQL Server como repositorio de datos mientras el otro utiliza bases de datos de Oracle).
Este patrón de diseño es capaz por si solo, bajo mi honesta opinión, de incrementar dramaticamente la calidad de practicamente cualquier arquitectura de software actual, basicamente por la modularidad y simplicidad de desarrollo, despliegue y configuración que propociona.
Uno de los grandes y ocultos tesoros del .NET Framework 2.0 es la existencia de un framework del patrón proveedor, Provider Model, que permite crear proveedores personalizados para cualquier tarea (no solo para extender los proveedores de ASP.NET como Membership o Roles, sino para cualquier aplicación y servicio desarrollado en .NET)
Usando el Provider Model de .NET Framework 2.0
Bueno, empezemos a ver algo de código. Para crear nuestro proveedor primero tenemos que crear un clase abstracta que defina nuestro contrato. Esta clase tiene que implementar la clase abstracta System.Configuration.ProviderBase y debe contener la definición de métodos y propiedades usadas como "contrato" de nuestro proveedor.
public abstract class ImageProvider : ProviderBase
{
public abstract bool CanSaveImages { get; }
public abstract Image GetImage(int id);
public abstract void SaveImage(Image image);
}
Despues necesitamos incluir la implementación del proveedor. En este caso, añadiremos un proveedor de imagenes que sea capaz de gestionarlas en el sistema de archivos (disco) y además añadiremos otro proveedor que se capaz de gestionarlas en Sql Server.
public class FileSystemImageProvider : ImageProvider
{
public override bool CanSaveImages
{
get
{
return true; // As we allow to save images
}
}
public override Image GetImage(int id)
{
// Some witty code to get the image from a folder in the FileSystem
return null;
}
public override void SaveImage(System.Drawing.Image image)
{
// Some witty code to save the image to a folder in the FileSystem
}
}
public class SqlImageProvider : ImageProvider
{
public override bool CanSaveImages
{
get
{
return true; // As we allow to save images
}
}
public override Image GetImage(int id)
{
// Some witty code to get the image from a folder in the FileSystem
return null; ;
}
public override void SaveImage(System.Drawing.Image image)
{
// Some witty code to save the image to a folder in the FileSystem
}
}
Finalmente, necesitamos crear el servicio que usa el proveedor ImageProvider. Este servicio esta ligado, o acoplado, a la definición del contrato de nuestro proveedor, pero no a las implementaciones concretas del mismo; de este modo conocerá las funciones que puede realizar pero no cómo se realizan. El servicio simplemente usa el método LoadProvider() para inicializar el proveedor que necesita (es importante tener en cuenta que el proveedor debería leerse de la configuración, y que en el ejemplo mostrado se especifica "a pelo" con propositos educativos; es necesario cambiar el código de ese método para cargar el proveedor dinamicamente de la configuración).
public class ImageService
{
private ImageProvider _provider;
public ImageProvider Provider
{
get
{
return _provider;
}
}
public ImageService()
{
LoadProvider();
}
public void LoadProvider()
{
ProviderSettings ps = new ProviderSettings("FileSystemImageProvider", "FileSystemImageProvider_Type_Assembly");
_provider = ProvidersHelper.InstantiateProvider(ps, typeof(FileSystemImageProvider)) as ImageProvider;
}
public Image GetImage(int id)
{
return _provider.GetImage(id);
}
public void SaveImage(Image image)
{
_provider.SaveImage(image);
}
}
El principal incoveniente de usar el Provider Model de .NET Framework 2.0 es que hay que referenciar el assembly System.Web en proyectos que pueden ser no-web. Es necesario para acceder a la clase ProvidersHelper que instancia la colección de proveedores. Además, fuerza el uso del namespace System.Configuration para acceder a la definición del proveedor concreto a utilizar, ubicado "a la fuerza" en el fichero web.config o app.config de nuestra aplicación.
En cualquier caso, tendrás que lidiar con los tipos y nombre del assembly donde se implementan, con el objetivo de crear los ProviderSettings y pasarlos al metodo ProvidersHelper.InstantiateProvider que devuelve la instancia del proveedor concreto que deseamos utilizar. Quizás no sea recomendable usar estas referencias a los namespaces "web" en contextos de servicios o aplicaciones de escritorio, y, obviamente, existe una alternativa bastante limpia para los más puristas del contexto y la orientación a objetos :)
La implementación OO pura
Vale, necesitamos eliminar la referencia a System.Web y hacer que el proveedor funcione sin el modelo presentado en el .NET Framework 2.0. El primer paso es refactorizar la clase abstracta (el contrato) a un interfaz (o también dejarlo como una clase, da igual, pero hay que eliminar la herencia o implementación de la clase ProviderBase y cualquier referencia al namespace System.Configuration) y después cambiar las implementaciones concretas de los proveedores (FileSystemImageProvider y SqlImageProvider) para que usen el interfaz que acabamos de refactorizar. El diagrama de clases debería quedar tal que así:

Primero debemos crear la definición del contrato del proveedor:
public interface IImageProvider
{
bool CanSaveImages { get; }
Image GetImage(int id);
void SaveImage(Image image);
}
Después, por supuesto, cambiar la definición de las implementaciones concretas de los proveedores FileSystemImageProvider y SqlImageProvider para que hagan uso del nuevo interfaz, del nuevo contrato:
public class FileSystemImageProvider : IImageProvider
{
// Implementation of the provider code, only signature of the class changed
}
public class SqlSystemImageProvider : IImageProvider
{
// Implementation of the provider code, only signature of the class changed
}
Y finalmente, en el servicio ImageService que permanece practicamente intacto, solo hace falta modificar el código del método LoadProvider para que cree una instancia del proveedor especificado sin utilizar la clase ProvidersHelper. Algo como esto funcionaría perfectamente:
public void LoadProvider()
{
_provider = Activator.CreateInstance(typeof(FileSystemImageProvider)) as IImageProvider;
}
¿Alguna conclusión?
Como puedes ver, el método es muy similar en ambos modelos y además es muy sencillo de implementar en la mayoría de arquitectura de software modernas. Nos proporciona facilidad de instalación, sistemas muy desacoplados y modularidad, tanto a nivel de desarrollo como de despliegue.
No hay escusa para no utilizarlo, siempre que sea posible y práctico en el contexto de vuestra arquitectura :)
Espero que sirva de ayuda a alguien.
Archivado en: c#,.net,patrones,arquitectura,provider pattern
Comparte este post: