Patrones de diseño: el singleton

El singleton es el patrón de diseño creacional que propone manejar una sola instancia para ciertas clases. Este es uno de los patrones de diseño más conocidos y tal vez el primero que muchos llegamos a descubrir.

La ventaja de su uso será principalmente la reducción en el consumo de memoria. Sin embargo, también ayudará para enfocarse en el funcionamiento de los objetos, dejando de lado el cómo crearlos.

Existen varias formas de implementar este patrón. Una receta sencilla para su uso será:

  • Crear una variable estática del mismo tipo de la clase, una auto referencia donde se guardará la instancia del objeto.
  • Remover o restringir el acceso a aquellos métodos que puedan crear otra instancia de la clase, por ejemplo constructores o clonables.
  • Crear un método estático, que llevará la responsabilidad de crear y dar acceso a la instancia única de la clase.
<?php
class Singleton {
    
    private static $instance = NULL;
   
    private function __construct() {}
    private function __clone() {}

    public static function getInstance() {
        if (is_null(self::$instance)) {
            self::$instance = new Singleton();
        }

        return self::$instance;
    }
}

Muy bonita tu clase, pero ¿como lo llevo a la práctica?

El singleton puede usarse en cualquier clase de la que no se requiera más de una sola instancia. Un buen ejemplo, es para las conexiones a las bases de datos, donde bastará con tener un objeto que se encargue de todas las consultas.

Hay que tener en cuenta que este patrón de diseño va muy de la mano con el principìo de responsabilidad única. Cada objeto deberá contar con solo lo necesario para cumplir su función, lo que hará más sencilla su implementación.

A continuación muestro un ejemplo donde, el uso de varias implementaciones de singleton reduce la cantidad de objetos creados durante el proceso.

<?php
// index.php
require_once "UserOperations.php";
require_once "ProductOperations.php";

$userOperations = UserOperations::getInstance();
$productOperations = ProductOperations::getInstance();
var_dump($userOperations->getUserData('1'));
var_dump($productOperations->getCount());
<?php
// UserOperations.php
require_once "DatabaseConnection.php";

class UserOperations {
    private static $userOperations;

    private $connection;

    private function __construct($connection) {
        $this->connection = $connection;
    }


    public static function getInstance() {
        if (is_null(self::$userOperations)) {
            $userOperations = new userOperations(
                DatabaseConnection::getConnection()
            );
            self::$userOperations = $userOperations;
        }

        return self::$userOperations;
    }

    public function getUserData($id) {
        $data = $this->connection
            ->query("SELECT * FROM users WHERE id = $id")
            ->fetch_row();

        return $data;
    }
}
<?php
// ProductOperations.php
require_once "DatabaseConnection.php";

class ProductOperations {
    private static $ProductOperations;

    private $connection;

    private function __construct($connection) {
        $this->connection = $connection;
    }

    public static function getInstance() {
        if (is_null(self::$ProductOperations)) {
            $ProductOperations = new ProductOperations(
                DatabaseConnection::getConnection()
            );
            self::$ProductOperations = $ProductOperations;
        }

        return self::$ProductOperations;
    }

    public function getCount() {
        $data = $this->connection
            ->query("SELECT COUNT(*) FROM products")
            ->fetch_row();

        return $data[0];
    }
}
<?php
// DatabaseConnection.php
class DatabaseConnection {
    private static $databaseConnection;

    private $mysqli;

    private function __construct(
        $host,
        $user,
        $password,
        $databaseName
    ) {
        $this->mysqli = new mysqli(
            $host,
            $user,
            $password,
            $databaseName
        );
    }

    public static function getConnection() {
        if (is_null(self::$databaseConnection)) {
            $databaseConnection = new DatabaseConnection(
                "127.0.0.1",
                "root",
                "",
                "test"
            );
            self::$databaseConnection = $databaseConnection;
        }

        return self::$databaseConnection->mysqli;
    }
}

Del anterior ejemplo, hay que resaltar que la clase DatabaseConnection solo es instanciada una vez a pesar de que tanto UserOperations y ProductOperations hacen uso de ella.

¿Se podría separar de la clase la responsabilidad de que solo exista una instancia?

Claro, si te das cuenta, en el ejemplo anterior, las tres clases usarán siempre los mismos valores para su creación. Ademas los métodos para obtener la instancia son muy similares.

Piensalo de esta forma, si se guarda en una lista la información que usan los constructores de cada clase, entonces se puede hacer que a partir de ella se creen las instancias.

Esto es algo que ya ha sido pensado e implementado en frameworks como Symfony. El container que usa, puede llamar a la instancia de cualquier clase que esté listada como servicio en archivos de configuración.

Conclusión

El singleton tiene mucho potencial en las aplicaciones mientras se sigan prácticas de separación de responsabilidades. Se puede tener clases pequeñas con funciones específicas en lugar de grandes bloques de código con alta complejidad.

Deja un comentario

Blog de WordPress.com.

Subir ↑