Patrón Singleton en PHP5

Patrón Singleton en PHP5






El patrón de diseño"Singleton" está diseñado para restringir la creación de objetos pertenecientes a una clase, forzando a que solo se pueda crear una única instancia, de ahí su nombre Singleton (instancia única).






Un ejemplo de uso de este patrón: supongamos que tenemos una clase "Database" y instanciamos dicha clase, la cual nos devuelve un objeto llamado "$conn", una vez creada la conexión con la base de datos esta simple instancia será fácilmente accesible a muchos otros objetos, restringiéndose así a la creación de una única instancia la cual será reutilizada una y otra vez.

¿Pero como funciona el patrón de diseño Singleton?

El patrón Singleton provee una única instancia global debido a que: si se solicita una instancia de la clase:


a) La propia clase es la responsable de crear la única instancia, la cual debe garantizar que no se pueda crear ninguna otra (interceptando todas las peticiones para crear nuevos objetos) y proporcionando un solo punto de acceso a ella.

b) Si no se ha creado anteriormente (o sea, es la primera vez que se usa esta clase) se crea la instancia.

c) Si existe ya anteriormente una instancia (es la segunda o más veces que se usa), se retorna la existente todas las veces que se solicite.

d) El constructor de la clase debe declararse como privado para que la clase no pueda ser instanciada directamente.

e) Adicionalmente en PHP5 no se permite que la clase sea clonada con el método __clone().

De esta forma forzamos a que no se puedan crear objetos de forma directa por la vía del operador "new" ejemplo:

$objeto = new Ejemplo();
//Fatal error:Call to private Ejemplo::__construct() from invalid context

Bueno llegados a este punto se pudieran preguntar

¿Entonces como creo la instancia a la clase si el constructor está declarado como privado?

Directamente a través de un método declarado como público y estático, que será el encargado de crear la instancia de la clase. Ver a continuación:

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

Cuando declararamos atributos de clases o métodos como estáticos, los hacemos accesibles desde afuera del contexto del objeto. Utilizamos entonces el operador de resolución :: que permite el acceso a los atributos y métodos estáticos, veamos un ejemplo:

$var = NombreClase::Metodo();

Funcionamiento del método getInstance()

Primeramente accedemos al método:

$inst1 = Ejemplo::getInstance();


Como podemos ver este método verifica el estado del atributo private static $instance;  si está o no definido, para así tomar la decisión de crear la instancia o retornarla directamente en caso de que ya esté creada.

Entonces existen 2 posibles casos:

Caso 1: Si no esta definido el atributo $instance se crea por primera vez la instancia, se almacena en el la referencia y se retorna.


Caso 2: De estar definido el atributo se retorna la instancia almacenada en el directamente.

Implementacion oficial del Patrón Singleton en PHP5:

Esta es la implementación oficial en PHP5 de este patrón les dejo el vinculo http://www.php.net/manual/en/language.oop5.patterns.php , lo único que he cambiado por motivos didácticos y de claridad es el nombre del método singleton() por el de getInstance().

  
class Ejemplo
{
/**
*  Contenedor de la Instancia de la Clase
*
*  @access private static
*/
private static $instance;

/**
*  Constructor
*
*  Previene la creación de objetos via $obj = new Ejemplo();
*
*  @access private
*/
private function __construct() { }

/**
* El método getInstance
*
* Crea una instancia de la clase Ejemplo
*
* @access public static
* @return object de la clase Ejemplo
*/
public static function getInstance()
{

if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;

}

return self::$instance;
}
/**
* El método __clone()
*
* Previene que la clase sea clonada
*
* @access public
*/
public function __clone()
{
trigger_error('Clone no se permite.', E_USER_ERROR);

}
}

/**
* Ejemplo de uso
*
* Accedo directamente al método getInstance()
* para que me devuelva una instancia de la clase Ejemplo,
* como dicha instancia no existe este método, la crea.
*
* Nota: si hacemos var_dump sobre un variable esta función
* mostrará información estructurada que incluye tipos y valores.
* En el caso de las variables que contienen objetos esta funcion
* devolvería tipo de la variable, en este caso
* object, entre paréntesis (nombre de la clase) y
* un número único #id que representa al objeto creado.
* Podemos ver que dicho número #id es el mismo para estas
* dos primeros casos.
*/
$inst1 = Ejemplo::getInstance();


var_dump($inst1);
/** * En el segundo caso, como existe ya la instancia, * el método getInstance() lo comprueba y no la crea, * sino que la retorna directamente. */ $inst2 = Ejemplo::getInstance();
var_dump($inst2);
/** * Si intento crear una instancia directamente utilizando * el operador new. * * Fatal error: Call to private Ejemplo::__construct() * from invalid context */ $inst3 = new Ejemplo();
¿Ventajas y usos?

Suponiendo que utilizamos el Singleton en una Base de Datos, esto posibilita que:

1.) Que nuestra aplicación web ahorre recursos del servidor.
2.) Tenemos bien controlado la creación de objetos, al no permitir que nuestra aplicación tenga innumerable y descontroladas conexiones innecesarias abiertas a una misma base de datos.
Bibliografía
  1. Enrique Place, (7 de noviembre del 2006). «Patrón de Diseño "Singleton"». Consultado el 25 de agosto de 2009.
  2. Colaboradores de Wikipedia. Patrón de diseño [en línea]. Wikipedia, La enciclopedia libre, 2009 [fecha de consulta: 17 de septiembre del 2009]. Disponible en <http://es.wikipedia.org/w/index.php?title=Patr%C3%B3n_de_dise%C3%B1o&oldid=29814016>. 
  3. Colaboradores de Wikipedia. Singleton [en línea]. Wikipedia, La enciclopedia libre, 2009 [fecha de consulta: 1 de septiembre del 2009]. Disponible en <http://es.wikipedia.org/w/index.php?title=Singleton&oldid=29359982>.

    3 comentarios:

    1. Excelente artículo, te felicito. Me gustaría saber si seguirás tratando con este tema de los patrones.

      ResponderEliminar
    2. Muy bien explicado y detallado todo, no se encuentran muy a menudo información tan valiosa como esta y mucho menos sobre el tema de Patrones de Diseño, un saludo y muchas gracias.

      ResponderEliminar
    3. Hola. muy bueno el articulo.También leí algo que has escrito es n otro blog con GATORV
      Mi duda es la siguiente:

      A)En clase dbconnbo tengo:



      public static function getInstance()

      {

      if (!isset(self::$instance)) {

      $c = __CLASS__;

      self::$instance = new $c;

      }



      return self::$instance;

      }



      Y luego la conexion:

      public function setDbConnection($dns, $resu, $ssap) {



      $link = new PDO($dns, $resu, $ssap, array(PDO :: ATTR_PERSISTENT => true));



      if ($link) {

      $link->exec("set names utf8");

      $this->_dbconnection = $link;

      }

      return $this->_dbconnection;

      }





      Despues en usuarios tengo



      public static function obtenerTodos() {



      1 $pdo= DbConnBO::getInstance();

      2 $cnn= $pdo->setDbConnection(DB_HOST, DB_USER, DB_PASS);


      Con lo cual obtendría una instancia de la clase dbconn (que ademas tiene otros métodos, como por ejemplo insertSQL($sql,$tabla)) ¿Pero que pasa ocn la línea 2?



      ---------------------------------------------------------------------------------------------------------

      Eso por un lado. Pero tambien podría hacerse de la siguiente manera:



      B)En clase dbconnbo tengo:



      public static function getInstance($snd, $resu, $ssap){



      if (!isset(self::$instance)) {



      self::$instance = new PDO($snd, $resu, $ssap);

      self::$instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

      }

      return self::$instance;

      }





      Y despues en tengo usuarioList(por jemplo):





      public static function obtenerTodos() {



      $cnn= DbConnBO::getInstance(DB_HOST, DB_USER, DB_PASS);





      Lo cual entiendo que utilizaría simpre la misma conexión.

      Fucnoinar, funciona de las dos maneras. Lo que no me queda muy claro es cuál sería más eficiente

      ResponderEliminar