Patrón de Diseño "Singleton" (revisado 7/11/2006)


El Patrón "Singleton" sirve para cuando buscamos restringir la creación de instancias de un objeto, obligando que solo se pueda crear una única instancia (de ahí su nombre).

Un caso hipotético de aplicación podría ser: tenemos una clase BaseDeDatos que nos devuelve un objeto llamado "bd" una vez creada la conexión con la base de datos. En vez de permitir que las aplicaciones usen libremente la clase y puedan crear tantas instancias del objeto "Base de Datos" como aplicaciones y accesos a bases existan, se decide restringir la creación a una sola instancia y esta será compartida y usada por todos.

¿Cómo funciona el patrón de diseño Singleton?

La forma de proceder del patrón es la siguiente: si se solicita una instancia del objeto:

a) si no existe (o sea, la primera vez que se usa) se crea la instancia.

b) si existe una instancia (es la segunda o más vez que se usa), devuelvo la existente, todas las veces que se me solicite.

c) el constructor de la clase debe permanecer "anulado" definiéndolo como "privado". De esta forma se asegura que no se puedan crear instancias de forma directa y solo se permite a través del método "getInstancia()":

class Singleton {
    static
private $instancia = NULL;

   private function __construct() {}

    static
public function getInstancia() {
       if (
self::$instancia == NULL) {
         
self::$instancia = new Singleton ();
       }
       return
self::$instancia;
    }
}
// Ejemplo de uso

// Le pido al Singleton que me de una instancia
// del objeto. Como no existe, la crea.

// Nota: si hacemos "echo" de un objeto que no
// tiene implementado el método toString,
// el sistema despliega el número único que
// representa al objeto creado.

$inst1 = Singleton::getInstancia();
echo
$inst1;

// En el segundo caso, como existe la instancia,
// no la crea, y la entrega directamente.

$inst2 = Singleton::getInstancia();
echo
$inst2;


¿Ventajas/usos?

Como dice la introducción, tenemos controlada la creación de objetos y podremos además disminuir el uso de memoria al tener una sola instancia que se usa en todo el contexto de la aplicación.

23 comentarios:

Anónimo dijo...

hola, una consulta:
con respecto al script que publicaste: te pongo la siguiente situación:
tienes:

admin.php (se encarga de administrar usuarios de un ldap, por ejemplo)
Ldap.php (clase q administra conexion y acciones sobre directorio Ldap

si yo ejecuto por primera vez admin.php, me crea la instancia única de Ldap, en eso estamos de acuerdo..pero si yo le doy la orden de ingresar un nuevo usuario
tengo q refrescar la pagina admin.php (este script se encargará de ver que accion se desea realizar y mandar los parámetros correspondientes a los metodos implementados en la clase para el manejo de Ldap) se pierde la instancia de Ldap.php?? es decir, si hago 20 acciones crea 20 instancia o siempre mantiene en memoria una sola? si me cambi oa admin2.php, pierdo la instancia?? asi como lo que se hace en java, que crea la instancia unica hasta q uno mismo la destruye?? solo funciona para php5, en 4 no hay nada hecho asi?..

enrique_place dijo...

Si mal no interpreto tu pregunta, el centro de tu duda está en saber cual es el alcance de la vida de un objeto en la ejecución de uno o más scripts de PHP ... la respuesta es: el objeto vive lo que dura la ejecución de un solo scripts.

Si tienes un admin.php, y quieres probar el patrón Singleton, solo funcionará si invocas más de una vez la llamada al patrón pero dentro de la duración del propio scripts.

Por ejemplo:

// Le pido al singleton que me de una instancia del objeto, como no existe, la crea.

$inst1 = Singleton::getInstance();
echo $inst1;

// En el segundo caso, como existe la instancia, no la crea, me la entrega directamente.

$inst2 = Singleton::getInstance();
echo $inst2;

// Ejemplo de un caso normal, donde no existe el patrón.

class Persona{};

$comun1 = new Persona();
echo $comun1;

$comun2 = new Persona();
echo $comun2;

Retorna:

Object id #1
Object id #1
Object id #2
Object id #3

Primero, como no tienen definido el método "toString" cuando hacemos "echo" de un objeto, nos devuelve un identificador único del objeto.

En los dos primeros, como se aplica el singleton, las "id" son iguales (se reusó la instancia primera).

En los dos casos siguientes, usan el número "id" siguiente, 2 y 3, porque no se usa el patrón y son dos instancias de objetos distintas.

Y nuevamente, si volvemos a restaurar nuestra página, el resultado será igual, pues luego de terminar de ejecutarla, los objetos se destruyen y volvemos a empezar de cero.

El patrón Singleton no hace magia, es lo mismo que cualquier otra clase de PHP creada por ti. Si termina la ejecución del PHP, la clase deja de existir.

¿Quedó ahora más claro?

Duilio dijo...

Excelente. Necesitaba esto. Estoy haciendo un Framework y tenía un dolor de cabeza con el asunto "no tan hipotético" de las multiples instancias de mi clase de base de datos. Adicionalmente es posible que me sirva para otras cosas.

Como mi biblioteca tiene nombres de constelaciones. Creo que esta será Fenix.

Un abrazo y gracias por el articulo

enrique_place dijo...

Estimado Duilio:

De nada y muy contento que te haya sido útil!

Saludos

Eugenio dijo...
Este comentario ha sido eliminado por el autor.
Patricia dijo...

Tengo problemas con este patron.. yo tengo mi libreria class Administrador.inc.php y es asi:

class Administrador {
var $idAdmin;
var $login;
var $pass;

//para el patron singleton
static private $admin = NULL;

private function Administrador()
{
$login='';
$pass='';
}

static public function getInstancia() {
if (self::$admin == NULL) {
//crea una vez solo el objeto
self::$admin = new Administrador ();
echo "crea una vez solo el objeto";
}
else echo "NOOOO crea una vez solo el objeto";
return self::$admin;
}

y lo creo en otra pagina administradores.php

$administrador = Administrador::getInstancia();

y tengo una var accion para controlar su curso y si tengo q pintar los admins pues hago:
$administrador-> mostrarAdministradores();

si tengo q crear pues:
$administrador->crearAdministrador();

entonces con cada accion cojo la instancia pero nunca entra por el caso en q admin no sea vacio!!!!
no se si me explico..
el patron singleton no se cumple!!!
siempre crea una instancia!

podeis ayudarme???

gracias

Ricardo dijo...

muy buen aporte...


cambie mi clase.. cambie los llamados a donde creaba otra vez el objeto de conexion.. y TARÁN... todo anduvo sin problemas!


muchas gracias!.. mejor explicado imposible!

Jeeba dijo...
Este comentario ha sido eliminado por el autor.
Jeeba dijo...

Una pregunta.. crees que quizas tener un Singleton que acceda a la base de datos, pueda crear un cuello de botella?, digamos si todos los usuarios usan el mismo objeto para entrar y sacar informacion, crees q seria un problema?

enrique_place dijo...

Estimado Jeeba:

> Una pregunta.. crees que
> quizas tener un Singleton que
> acceda a la base de datos, pueda
> crear un cuello de botella?,
> digamos si todos los usuarios usan
> el mismo objeto para entrar y
> sacar informacion, crees q seria
> un problema?

Habría que evaluar el contexto real y ver el costo que tiene esto. En sí no es lo mismo que el cuello esté en la base de datos que en el objeto de persistencia que hace la conexión a la base de datos.

Lo único que hace el singleton en este caso es compartir la misma conexión, que sería el mismo efecto que tener muchas conexiones abiertas para la base de datos (que a su vez tiene un límite configurable de conexiones abiertas).

No necesariamente el singleton sirve solo para este caso (puedes aplicarlo a otros problemas), tal vez en otras situaciones no te interesa hacer esto y sí que cada objeto tenga una conexión a la base.

Lo que hay que tener en cuenta es que en realidad tienes una conexión sola por página ejecutada (lo que no es nada malo) y evitas que para armar una página tengas varias conexiones por cada instancia de objeto que necesita algo de la base).

Si quisieras que todo tu sistema usara una única conexión, que ya es otro tema, no podrías hacerlo con este singleton por el tema del "stateless" (estado desconectado, ejecuta una página y todo se muere al final), deberías crearte una sesión propia que deberás persistir en algún medio para que cada página pueda recuperar la misma conexión.

Espero haber clarificado un poco más tus dudas :-)

Jeeba dijo...

Gracias, eso me clarifica grandemente las cosas. Excelente blog by they way =)

enrique_place dijo...

Estimado Jeeba:

Gracias por los comentarios, me alegro que te haya parecido claro ;-)

Pablo dijo...

Enrique: he visto varias versiones de la implementacion del patron Singleton y te quiero preguntar por tu funcion getInstance()

Yo me he conseguido esta otra que como veras verifica por !isset() en vez de comparar por NULL y la cosa es que no es lo mismo!!! cual esta bien ?

public static function getInstance () {
if (!isset(self::$instancia)) {
$obj = __CLASS__;
self::$instancia = new $obj;
}
return self::$instancia;
}


La otra duda es: el __CLASS__ se usa para que la funcion se pueda utilizar con cualquier nombr de clase, verdad?

Gracias desde ya!

// Pablo (italico76 de FDW)

Pablo dijo...

Que torpe que soy! sin palabras! :D

Veo que le asignas NULL al comienzo.... bueno.... igual no se porque lo haces asi.... pero debes tener tus razones :)

Salu2!

enrique_place dijo...

Estimado Pablo:

Lo que importa del patrón Singleton es el concepto, implementaciones pueden existir varias (tú lo debes ajustar luego a tu contexto y necesidades).

La idea es, la primera vez no existe (NULL), entonces crea la instancia, la segunda vez, si existe, retorna la instancia existente.

Puedes preguntar por isset, is_null, es lo mismo.

Lo del __CLASS__ no es determinante, te da el nombre de la clase actual, no veo que te aporte mucho a menos que quieras hacer algo genérico.

Lo de inicializar con NULL es más que nada "documentación", es lo mismo si no se hace.

Espero haber contestado todas tus dudas ;-)

Pablo dijo...

Enrique: muchas gracias! muy gentil como siempre :)

LEAD dijo...

para un singleton siempre se usa static?

LEAD dijo...

En programación sin objetos la solución de un singleton se asemejaria a usar variables de session o globales? por ejemplo llevar en una variable de session la conexion a la BD?

luka dijo...

Hola amigo, tu blog me ha sido muy util en numerosas ocasiones y por eso te lo agradezco. Te voy a hacer una pregunta ya que me encuntro con un problema. Tengo dos bases de datos en localhost. Para conectarme a una base de dato tengo una clase dbLink que tiene como atributos server, username, password y database. Luego tengo otra clase database que extende la dbLink y que en su constructor:
1) llama el constructor de su parente;
2) establece la conexion con la base de datos;
3) selecciona el database;

Luego siempre en esta clase database hay un metodo para ejecutar una query y devolver un array asociativo.

Ahora, en mi script test.php me instancio dos objetos:
$a= new database("localhost", "usernamepepito", "passwdpepito", "databaseA");
$b= new database("localhost", "usernamefulano", "passwdfulano", "databaseB");

El problema es q cuando voy a ejecutar una query sobre el objeto $a esa viene ejecutada en el database b.

El pattern singleton podria ayudarme a soluccionar mi problema, siempre tenendo en cuenta que en mi sistema tengo que mantener activas dos bases de datos? Muchas gracias...

Nexus dijo...

Saludos

Cuando sabria reconocerse que el patron singleton es viable, referente a saber cuando es factible usarlo y cuando no? por instancia unica uno podria pensar (supongo) que se desaria usarlo siempre, puede presentarse un cuello de botella? dependeria de la demanda de peticiones de conexion? determinar cuando es aconsejable usarlo?

Seria mejor usar variables globales o de session que singleton en algunos casos?

Muchas gracias por la asesoria del caso.
Saludos.

Eder dijo...

Hola!
Veo que hay una pequeña confusion en algunos comentarios, o mejor dicho, una duda en algunas preguntas... voy a intentar aclarar, con permiso del autor.

En verdad, singleton es mas utilizado en lenguajes que no destruyen los objetos mientras un programa esté en ejecucion, como por ej. lo hace Java (no quiero entrar en detalles como garbage collection).

En php, un objeto se destruye cuando la pagina termina de abrir (es decir, cuando el script termina de ejecutar).

Por ello, cuando se sale de una pagina a otra, se genera otro objeto. No se mantiene la misma instancia!

El singleton es util cuando se intenta instanciar un objeto dos o más veces en un mismo archivo!

En el caso especifico del ejemplo de base de datos, si se quiere mantener una informacion de la base para usar en el aplicativo al transitar por varias paginas, se puede hacer por sesion o cookies. Ej.: El nombre de un usuario.

Para el caso especifico de conexiones a la BD, si se quiere mantener apenas un enlace de conexion a la base de datos (es decir, no abrir una conexion para cada consulta que se realice) al transitar entre varias paginas o dentro de una misma pagina, lo que se puede hacer es :
1) una conexion persistente, como por ejemplo con la funcion mysql_pconnect.
2) cerrar las conexiones abiertas despues de realizar las consultas, como por ejemplo con la funcion mysql_close.

De las dos formas, hay que solicitar la conexion al abrir cada pagina.

Saludos!

Eduardo dijo...

Hola Enrique. Tengo algunas dudas sobre el patrón de diseño Singleton
1 Por que le llaman patron de diseño?
2 Una clase de este tipo puede llevar atributos?
3 Estos atributos pueden ser private protected y static?

Un saludo

Samuel Guzmán dijo...

Hola muchachos, Enrique, en referencia a una de tus respuestas mas arriba, dices que: el scrip se muere al terminar su ejecusion

Sabiendo que la naturaleza del Singleton es crear un objeto que sea (por decirlo asi) persistente y unico, de suerte y tal manera que pueda ser usado por todas las aplicaciones que lo requieran, entonces quiere decir que:

Por cada vez que una apliacion llame la clase, la misma siempre se va a crear, ya que segun su explicacion mas arriba, el objeto muere con la ejecusion de cada script, dandole solo una brecha de reusabilidad cuando por casualidad y circustancia mientras este vivo este objeto pueda ser pasado a cualquier otro scrip que lo requiera?

Entradas populares