Patrón Singleton (GoF)
Problema a resolver
Hay muchos casos en los que solo necesitamos una instancia de una determinada clase. Ejemplos típicos son clases que realizan la conexión con la base de datos, representan las preferencias del usuario o la configuración del sistema, o clases que sirven de interfaz con dispositivos físicos.
En estos casos hay que asegurarse de poder obtener una referencia a dicha instancia desde cualquier punto del código, y que solo haya una instancia creada, para evitar posibles problemas o inconsistencias. Una posible solución es definir variables globales (o sea, static), pero esto tiene dos problemas:
- Por descuido, se podría instanciar una misma variable en dos sitios distintos, lo que el valor anterior.
- El orden y momento de inicialización de las variables depende del parser, lo cual puede ser delicado si unas dependen de otras y están en lugares distintos.
Por lo tanto, el objetivo es restringir la creación de objetos pertenecientes a una clase o el valor de un tipo a un único objeto.
Solución
Una posible solución sería garantizar de alguna forma que una clase sólo tenga una instancia y proporcionar un punto de acceso global a ella.
Funcionamiento
El patrón **Singleton** nos permite asegurar que de una clase habrá solo una instancia, y proporciona un punto de acceso a ella global a todo el código. En la figura siguiente podemos ver un diagrama de clases muy sencillo, ya que se compone de una única clase:
Tenemos una variable **unicaInstancia** de tipo Singleton y un método estático llamado **getInstance()** que nos devuelve un objeto de tipo Singleton. De esta forma, creando en nuestra clase un método estático que cree una instancia del objeto sólo si todavía no existe todavía, podríamos asegurar que la clase no puede ser instanciada nuevamente si regulamos el alcance del constructor (con atributos como protegido o privado).
Consecuencias
La propia clase es responsable de crear la única instancia permitiendo el acceso global a la misma mediante un método de clase.
Para implementar este patrón debemos cumplir los siguientes requerimientos:
No debe tener un constructor público para controlar las instancias a esa clase (el constructor se declara como privado o protegido)
Las instancias se obtienen mediante el método getInstance() (en realidad siempre nos dará la misma)
La instancia está almacenada dentro de la propia clase como una variable estática (retorcido, pero legal).
Veamos como sería el diagrama UML del patrón Singleton:
"Idea feliz" para implementar un Singleton
Supongamos una clase con constructor privado:
public class MiSingleton {
private __constructor() {
//aqui va el codigo del constructor ...
}
}
Solo podemos llamar al constructor desde un **MiSingleton**, pero ¿cómo obtenemos la primera instancia del objeto **MiSingleton**?
Podríamos llamar al constructor privado a través de un método estático.
De esta forma:
public class MiSingleton {
private __constructor() {. . . }
public static function getInstance() {
return new MiSingleton;
}
}
Solo nos falta asegurarnos de que solo se crea una instancia
<?php
public class MiSingleton {
private static MiSingleton $unico = null;
private __constructor() {. . . }
public static function getInstance() {
//sino hemos creado aún el objeto
if (is_null (self::$unico) ) //self es el nombre de la clase lo instanciamos
self::$unico = new self();
return self::$unico;
}
}
Ejemplo clase Singleton en PHP
class DBManager
{
// Contenedor de la instancia de la Clase
private static $instance;
private $db;
//Previene creacion de objetos via new
private function __construct() { }
// Método para obtener el objeto singleton
public static function getInstance ()
{
if ( is_null ( self::$instance) ) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection () {
if (is_null($this->db)) {
$this->db = new PDO('mysql:host=localhost;dbname=uazon', 'user', 'pass');
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
}
return $this->db;
}
}
Ejemplo de uso dentro del fichero home.php:
...
$conexion = DBManager::getInstance()->getConnection();
$libroDAO = new LibroDAO($conexion);
$libros = $libroDAO->getLibros();
...
Otro ejemplo de una clase que contiene datos de configuración de una aplicación y que implementa el patron Singleton para mantener una única instancia y poder acceder a sus valores desde cualquier sitio.
class Config
{
private $vars;
private static $instance;
private function __construct() {
$this->vars = array();
}
public function set($name, $value) {
if(!isset($this->vars[$name])) {
$this->vars[$name] = $value;
}
}
public function get($name) {
if(isset($this->vars[$name])) {
return $this->vars[$name];
}
}
public static function getInstance(){
if (!isset(self::$instance)) {
$class = __CLASS__; //otra forma
self::$instance = new $class();
}
return self::$instance;
}
}
Ejemplo de uso:
...
$config = Config::getInstance();
$config->set('host', 'localhost');
echo $config->get('host'); //localhost
$config2 = Config::getInstance();
echo $config2->get('host'); //localhost