Patrón Singleton
Un Anillo para gobernarlos a todos
Hay patrones de diseño que, cuando los explicas, parece que fueron inventados específicamente para que podamos ponerles un ejemplo friki.
Y el Singleton es uno de ellos.
Porque si hablamos de una única instancia que debe existir en toda nuestra aplicación, ¿qué mejor ejemplo que el Anillo Único?
«Un Anillo para gobernarlos a todos, un Anillo para encontrarlos, un Anillo para atraerlos a todos y atarlos en las tinieblas.»
Aunque nosotros vamos a intentar utilizarlo para algo un poco menos peligroso.
¿Qué es el patrón Singleton?
El patrón Singleton es un patrón creacional que se utiliza cuando queremos asegurarnos de que una clase tenga una única instancia durante toda la ejecución de nuestra aplicación.
Además, nos proporciona un punto de acceso para poder obtener esa instancia desde cualquier lugar.
En otras palabras: Solo puede existir uno.
Y aquí es donde entra nuestro protagonista.
El Anillo Único
En la Tierra Media hay muchos anillos.
Tenemos los Anillos de Poder, los anillos de los Elfos, los de los Enanos...
Pero hay uno que es diferente.
El Anillo Único.
Y solo existe uno.
Imagínate que pudiéramos hacer esto:
$anillo1 = new AnilloUnico();
$anillo2 = new AnilloUnico();
Tendríamos un problema. Ahora tenemos dos Anillos Únicos.
Sauron estaría encantado, pero nosotros hemos roto completamente las reglas de la Tierra Media.
Lo que necesitamos es impedir que alguien pueda crear nuevas instancias de nuestra clase.
Implementando nuestro Singleton
Vamos a crear una clase muy sencilla:
class AnilloUnico
{
private static ?AnilloUnico $instance = null;
private function __construct()
{
}
public static function getInstance(): AnilloUnico
{
if (self::$instance === null) {
self::$instance = new AnilloUnico();
}
return self::$instance;
}
}
Ahora podemos obtener nuestro Anillo utilizando:
$anillo = AnilloUnico::getInstance();
Y si alguien vuelve a pedirlo:
$otroAnillo = AnilloUnico::getInstance();
No se crea otro objeto.
Ambas variables apuntan a la misma instancia.
var_dump($anillo === $otroAnillo);
El resultado será:
true
Un Anillo para gobernarlos a todos.
¿Qué está pasando aquí?
Aunque el código es bastante pequeño, hay varias cosas importantes.
El constructor es privado
private function __construct()
{
}
Esto es fundamental.
Al hacer que el constructor sea privado impedimos que desde fuera de la clase alguien pueda hacer:
new AnilloUnico();
La única forma de obtener el objeto será utilizando nuestro método getInstance().
De esta manera controlamos nosotros cuándo se crea la instancia.
Guardamos la instancia
Tenemos una propiedad estática:
private static ?AnilloUnico $instance = null;
Aquí almacenaremos nuestro Anillo.
Inicialmente no existe, por eso su valor es null.
La primera vez que llamamos a:
AnilloUnico::getInstance();
comprobamos si ya existe.
if (self::$instance === null) {
self::$instance = new AnilloUnico();
}
Como todavía no existe, lo creamos.
Pero las siguientes veces que llamemos a getInstance() ya tendremos una instancia creada.
Por lo tanto, simplemente la devolvemos.
return self::$instance;
¿Por qué utilizar Singleton?
El Singleton puede ser útil cuando realmente necesitamos que exista una única instancia de un determinado recurso.
Algunos ejemplos podrían ser:
- Un sistema de configuración.
- Un gestor de logs.
- Un gestor de caché.
- Un registro de eventos.
- Determinados recursos compartidos.
Por ejemplo, podríamos querer tener un único sistema de configuración:
$config = Config::getInstance();
$config->get('database.host');
Y asegurarnos de que toda nuestra aplicación está trabajando con el mismo objeto.
Pero... ¿deberíamos utilizarlo siempre?
Aquí viene la parte importante.
Que Singleton sea un patrón conocido no significa que tengamos que utilizarlo cada vez que necesitemos compartir un objeto.
De hecho, Singleton tiene bastantes detractores.
Al proporcionar un acceso global a una instancia, puede terminar creando un acoplamiento fuerte entre diferentes partes de nuestra aplicación.
También puede complicar las pruebas unitarias, ya que nuestras clases pueden terminar dependiendo directamente de una instancia global.
En aplicaciones modernas, muchas veces podemos conseguir el mismo comportamiento utilizando inyección de dependencias y dejando que nuestro framework o contenedor de dependencias gestione la instancia.
Por eso, antes de crear un Singleton, conviene preguntarnos:
¿Realmente necesito que exista una única instancia?
Si la respuesta es sí, puede tener sentido.
Si simplemente queremos compartir un objeto entre diferentes clases, probablemente existan alternativas mejores.
¿Y qué tiene que ver todo esto con el Anillo?
Volvamos a Mordor.
Tenemos:
$frodo = AnilloUnico::getInstance();
Después llega Gandalf:
$gandalf = AnilloUnico::getInstance();
Y finalmente Aragorn:
$aragorn = AnilloUnico::getInstance();
Podemos comprobarlo:
$frodo === $gandalf;
$frodo === $aragorn;
Todos están utilizando exactamente el mismo objeto.
No hay tres Anillos.
No hay dos.
Solo hay uno.
Y eso, llevado al mundo del desarrollo, es precisamente la idea detrás del patrón Singleton.
En resumen
El patrón Singleton nos permite garantizar que una clase tenga una única instancia y proporcionar un punto de acceso común a ella.
Para conseguirlo normalmente:
- Hacemos privado el constructor.
- Guardamos la instancia en una propiedad estática.
- Creamos un método para obtener esa instancia.
- Si ya existe, devolvemos la misma.
Como casi todos los patrones de diseño, lo importante no es aprenderse el código de memoria.
Lo importante es reconocer cuándo tenemos un problema que el patrón puede solucionar.
Y recuerda:
Un Anillo para gobernarlos a todos.
Pero si tu aplicación empieza a comportarse como Gollum diciendo «mi tesoro» cada vez que alguien intenta tocar una instancia...
Quizás ha llegado el momento de revisar si realmente necesitabas un Singleton. 😏
Seguiremos buscando patrones por nuestro código y, cuando se nos ocurra algún ejemplo friki con el que explicarlos...
lo traeremos a Mirlos.
Nos vemos en el próximo. 🐦