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:

Diagrama de clases del patrón Singleton

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:

Posible 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

results matching ""

    No results matching ""