lunes, 3 de mayo de 2010

El patrón singleton


Muchos problemas de rendimiento (performance) y de escalabilidad (scalability) ocurren si no manejamos correctamente las conexiones a las bases de datos, hay que tener en presente que las conexiones para acceder a una base de datos son un recurso que consume memoria y tiempo de procesador, por lo que solo se deben crear las conexiones necesarias para el funcionamiento de las aplicaciones y estas deben cerrarse tan pronto hayan sido ocupadas.
Una buena práctica es que todas las clases que acceden a los recursos de la base de datos utilicen siempre una única conexión sin importar el número de instancias que estas clases generen al ser ejecutadas dentro de una aplicación, una solución recomendada a este escenario es utilizar los patrones de diseño de software.


Conectarse a una base de datos con el patrón Singleton


Es un patrón de diseño del tipo creacional que nos asegura la creación de únicamente la instancia de un objeto sin importar el número de veces que se intenten crear más instancias de ese objeto.
citando el clásico libro de the gang of four Design Patterns:Elements of Reusable Object-Oriented Software, 1995, Pearson Addison Wesley "The Singleton design pattern must ensure a class only has one instance, and provide a global point of access to it"

Este patrón se utiliza para representar un objeto de hardware (por ejemplo una impresora), un componente del sistema operativo (un archivo o un directorio) o bien un recurso externo limitado (como una conexión a una base de datos), esta instancia revisará si ya ha sido creada, atenderá las peticiones de los objetos que la utilicen como un punto global de acceso (similar a una variable global) y sera responsable del mantener el seguimiento de su estado.
El esqueleto clásico de un Singleton es el siguiente:

A continuación voy a mostrar el ejemplo de una clase que representa una conexión a una base de datos sin utilizar el patrón Singleton, para mostrar como se crea una conexión por cada ocasión que nosotros llamamos a la clase lo cual repercute en la escalabilidad y el desempeño de la aplicación si se tuvieran que invocar decenas o cientos las ejecuciones a esta clase.


A continuación el programa principal:

using System;
using System.Collections.Generic;
using System.Linq;

namespace PostSingleton
{
public class Program
{
static List _conns = null;
public static int Main(string[] args)
{
_conns = new List();
string op = null;
Console.ForegroundColor = ConsoleColor.White;
do{
Console.Write("\n");
Console.WriteLine("+-------------------------------+");
Console.WriteLine(" Ejemplo del patron singleton para conexiones");
Console.Write("\n");
Console.WriteLine("a) Conexión DB");
Console.WriteLine("b) Ver Conexiones");
Console.WriteLine("q) Salir");
Console.Write("\n");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Elija su opción y pulse : ");
Console.ForegroundColor = ConsoleColor.White;
op = Console.ReadLine().ToLower();
switch(op){
case "a":
ConectarDB();
break;
case "b":
VerConexiones();
break;
}
}while(op != "q");
Console.WriteLine("\nHasta luego");
return 0;

}
static void VerConexiones(){
try{
Console.Clear();
Console.WriteLine("\nListado de conexiones");
Console.Write("\n");
Console.WriteLine("+------------------------------------+");
Console.WriteLine("| CONEXIONES ACTIVAS |");
Console.WriteLine("+------------------------------------+\n");
if(_conns.Count > 0){
var q = from c in _conns where c.IsOpen == true select c;
foreach(var i in q){
Console.WriteLine("{0}",i.Info);
}}else Console.WriteLine("NO HAY CONEXIONES ACTIVAS");
}catch(Exception x){ Console.WriteLine("Excepcion " + x.Message); }
}
static void ConectarDB(){
try{
Console.Clear();
Console.WriteLine("Datos de la cadena de conexion ");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Servidor y pulse : ");
string server = @Console.ReadLine();
Console.Write("Base de datos y pulse : ");
string database = @Console.ReadLine();
Console.Write("Usuario y pulse : ");
string user = @Console.ReadLine();
Console.Write("Password y pulse : ");
string password = @Console.ReadLine();
Console.ForegroundColor = ConsoleColor.White;
string connStr = String.Format(
"Data Source={0};Initial Catalog={1};User ID={2};Password={3};",
server,database,user,password);
Console.WriteLine("Intentando conexión...");
DataBase db = new DataBase(connStr);
_conns.Add(db);
VerConexiones();
}catch(Exception x){
Console.WriteLine("Excepción " + x.Message);
}
}
static void Exit(){
Environment.Exit(0);
}}}

Al compilar y ejecutar la aplicación se mostrará el siguiente menú.

Escogiendo la opción (a) nos mostrará el menú para solicitarnos los parámetros de la cadena de conexión, si repetimos este procedimiento nos mostrará
las conexiones hechas hacia el servidor, una por cada vez que hayamos completado la opción (a) como se muestra en la siguiente imagen.



Observamos en la imagen como es diferente el tiempo entre cada una de las conexiones ya que se crea una conexión por cada llamado.



Bien ahora mostraremos el mismo ejercicio utilizando el patrón Singleton en el código de la clase DataBase, pero para no sobrescribir la clase DataBase del ejercicio anterior, nombraremos la clase Singleton como DataBase2.



Analizamos la clase y vemos el uso de los modificadores private y static en el constructor hace que no se pueda crear la clase de forma directa, sino solo a traves del método public GetInstance(string conStr) que regresa la instancia creada y por ende sus propiedades y métodos públicos.
Antes de ejecutar el programa principal para que utilice la clase con el singleton, hay que modificar la líneas donde se crea la lista de conexiones

static List _conns = null;
_conns = new List();

y tambien donde se invoca la conexión con la base de datos.

_conns.Add(DataBase2.GetInstance(connStr));

Haciendo estos cambios, ejecutando la aplicación, seleccionando la opción (a) para conectarse a una base de datos y creando varias conexiones debemos de ver
la salida del programa como en la siguiente pantalla:

Observamos en la imagen como el tiempo entre cada una de las conexiones es siempre el mismo, ya que se crea una sola instancia global de acceso.

 Descarga el código fuente