No, no os voy a hablar de moda sino de patrones de diseño. Si pensáis que son algo estupendo y que todos los desarrolladores de software deberían conocerlos, estamos de acuerdo, pero creo que hay un pequeño problema con ellos. Hay mucha gente que los conoce y no sabe muy bien cómo y cuándo aplicarlos, por lo que realiza una mala implementación que no resuelve ningún problema sino que complica el código de la aplicación. Queda muy bien decir: he usado el patrón Singleton, pero ¿sabemos cuándo hay que usarlo o sólo lo usamos porque queda bien?
Hace ya diez años estuve trabajando en un equipo de arquitectos de software de varios países realizando el diseño técnico de una aplicación corporativa para una gran multinacional. Un arquitecto francés pretendía utilizar TODOS los patrones de diseño conocidos porque según él eran las best practices recomendadas por Microsoft y una buena aplicación tenía que implementarlos todos. Me costó mucho trabajo convencerle de que los patrones de diseño resuelven problemas concretos y no se aplican necesariamente a todas las aplicaciones. Todavía hay mucha gente que sigue pensando que una aplicación es mejor o peor en función de cuántos patrones de diseño implemente, y personalmente pienso que es un error.
Hace poco estuve trabajando en un proyecto revisando el código fuente y me encontré cosas curiosas, como un patrón Singleton de un objeto que devuelve la conexión a un web service. La idea es buena, pero la implementación no tanto. El objeto no tenía estado ni almacenaba la conexión para reutilizarla, por lo que el patrón Singleton perdía todo su sentido. Sólo tenía un método que creaba y devolvía la conexión. Era algo así:
public class ServiceProvider { private static ServiceProvider _current = new ServiceProvider(); public static ServiceProvider Current { get { return _current; } } public IService GetConnection() { ClientCredentials credentials = new ClientCredentials(); if (ConfigurationSettings.AppSettings["username"] != null) credentials.Windows.ClientCredential = new NetworkCredential( ConfigurationSettings.AppSettings["username"], ConfigurationSettings.AppSettings["password"], ConfigurationSettings.AppSettings["domain"]); else credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials; Uri ServiceUri = new Uri(ConfigurationSettings.AppSettings["ServiceUri"]); ServiceProxy serviceProxy = new ServiceProxy(ServiceUri, null, credentials, null); IService service = (IService)serviceProxy; return service; } }
Se podría haber guardado la conexión como variable privada del objeto para devolverla en las siguientes llamadas en lugar de crearla de nuevo, y entonces sí hubiera tenido sentido el uso del patrón Singleton. Para empeorar la implementación, cuando se llama al objeto no se utiliza la propiedad Current para acceder a la instancia del objeto Singleton, sino que se realiza un new cada vez.
var service = new ServiceProvider(); var connection = service.GetConnection();
Otro problema de esta implementación es que ese objeto conexión se pasa como parámetro a algunos métodos, pero en otros no se pasa como parámetro sino que dentro de la función se vuelve a crear el objeto ServiceProvider y se vuelve a llamar al método GetConnection para obtener una nueva conexión.
¿Cuál hubiera sido una buena implementación? Bueno, el patrón Singleton se utiliza cuando queremos tener una única instancia de un objeto y reutilizarla desde toda nuestra aplicación. El motivo por el que querríamos reutilizar un objeto es que contiene algún estado o unos recursos (como una conexión a un web service) que queremos compartir. Una propiedad estática (como Current o Instance) o un método estático nos permiten acceder a la instancia sin necesidad de crear el objeto. Además, una característica importante del patrón Singleton es que no se permite la creación del objeto con new, lo que podemos conseguir poniendo el constructor privado. Una posible implementación utilizando el patrón Singleton podría ser la siguiente:
public class ServiceProvider { private IService _service = null; private static ServiceProvider _current = new ServiceProvider(); public static ServiceProvider Current { get { return _current; } } //Constructor is private private ServiceProvider() { } public IService GetConnection() { if (_service == null) { //Get credentials ClientCredentials credentials = new ClientCredentials(); if (ConfigurationSettings.AppSettings["username"] != null) credentials.Windows.ClientCredential = new NetworkCredential( ConfigurationSettings.AppSettings["username"], ConfigurationSettings.AppSettings["password"], ConfigurationSettings.AppSettings["domain"]); else credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials; //Create connection Uri ServiceUri = new Uri(ConfigurationSettings.AppSettings["ServiceUri"]); ServiceProxy serviceProxy = new ServiceProxy(ServiceUri, null, credentials, null); _service = (IService)serviceProxy; } return _service; } }
En cada función que utiliza la conexión podríamos utilizar las siguientes líneas:
IService connection = ServiceProvider.Current.GetConnection();
Aunque en este caso yo optaría por una implementación un poco más simple:
public static class ServiceProvider { private static IService _service = null; //Constructor is private private ServiceProvider() { } public static IService GetConnection() { if (_service == null) { //Get credentials ClientCredentials credentials = new ClientCredentials(); if (ConfigurationSettings.AppSettings["username"] != null) credentials.Windows.ClientCredential = new NetworkCredential( ConfigurationSettings.AppSettings["username"], ConfigurationSettings.AppSettings["password"], ConfigurationSettings.AppSettings["domain"]); else credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials; //Create connection Uri ServiceUri = new Uri(ConfigurationSettings.AppSettings["ServiceUri"]); ServiceProxy serviceProxy = new ServiceProxy(ServiceUri, null, credentials, null); _service = (IService)serviceProxy; } return _service; } }
Con lo que la llamada quedaría un poco más sencilla:
IService connection = ServiceProvider.GetConnection();
Se puede conseguir más información sobre el patrón Singleton, qué problemas resuelve y algunos ejemplos de implementación, en el artículo de la Wikipedia: http://es.wikipedia.org/wiki/Singleton