PHP
Tutorial PHP: Patrones de diseño
Explora los patrones de diseño en PHP y descubre cómo mejorar la escalabilidad y flexibilidad del código mediante soluciones probadas.
Aprende PHP GRATIS y certifícateIntroducción a los patrones de diseño
Los patrones de diseño son soluciones probadas y documentadas para problemas comunes en el desarrollo de software. En el contexto de la programación orientada a objetos en PHP, estos patrones proporcionan una guía estructurada para abordar desafíos recurrentes, promoviendo la reutilización del código y facilitando la mantenibilidad de las aplicaciones.
La adopción de patrones de diseño permite crear sistemas más escalables y flexibles, al proporcionar un vocabulario común que mejora la comunicación entre los desarrolladores. Al enfrentar un problema de diseño, es probable que exista un patrón establecido que ofrezca una solución eficiente y probada.
Existen tres categorías principales de patrones de diseño:
- Patrones creacionales: Abordan la creación de objetos de manera flexible y reutilizable. Ejemplos incluyen Singleton, Factory Method y Abstract Factory.
- Patrones estructurales: Se centran en la forma en que las clases y objetos se componen para formar estructuras más complejas, como Adapter, Decorator y Facade.
- Patrones de comportamiento: Gestionan la interacción y responsabilidad entre objetos, facilitando la comunicación y el flujo de datos en el sistema. Ejemplos son Strategy, Observer y Command.
Implementar patrones de diseño en PHP ayuda a solucionar problemas como la dependencia excesiva entre clases, la dificultad para extender funcionalidades y la necesidad de reducir código duplicado. Por ejemplo, el patrón Observer permite que los objetos se suscriban a eventos y reaccionen cuando ocurren cambios, promoviendo un bajo acoplamiento.
A continuación, se muestra un ejemplo sencillo que ilustra la importancia de utilizar patrones de diseño. Supongamos que tenemos una aplicación donde queremos notificar a varios servicios cuando ocurre un evento específico:
<?php
class EventManager
{
private $observers = [];
public function attach($observer)
{
$this->observers[] = $observer;
}
public function notify($data)
{
foreach ($this->observers as $observer) {
$observer->update($data);
}
}
}
interface Observer
{
public function update($data);
}
class EmailNotifier implements Observer
{
public function update($data)
{
echo "Enviando notificación por correo electrónico: " . $data . "\n";
}
}
class LogWriter implements Observer
{
public function update($data)
{
echo "Escribiendo en el log: " . $data . "\n";
}
}
// Uso del patrón Observer
$manager = new EventManager();
$manager->attach(new EmailNotifier());
$manager->attach(new LogWriter());
$manager->notify("Se ha producido un evento importante.");
En este ejemplo, utilizamos el patrón Observer para notificar a diferentes servicios cuando ocurre un evento, sin que EventManager
conozca los detalles de cada uno. Esto promueve un bajo acoplamiento y facilita la extensibilidad, ya que podemos agregar nuevos observadores sin modificar el código existente.
Los patrones de diseño también ayudan a estandarizar las soluciones, permitiendo aprovechar las experiencias y conocimientos colectivos de la comunidad. Al adoptar estos patrones, se mejora la legibilidad y comprensión del código, facilitando el trabajo colaborativo y la incorporación de nuevos miembros al equipo.
Es importante recordar que los patrones de diseño no son recetas rígidas, sino guías flexibles que deben adaptarse a las necesidades específicas de cada proyecto. Usarlos de manera adecuada requiere comprensión y criterio, evitando su aplicación forzada en contextos inapropiados.
Además, en PHP, gracias a las características avanzadas del lenguaje, como los traits, interfaces y el soporte para programación orientada a objetos, es posible implementar patrones de diseño de manera eficiente y elegante. Esto permite crear aplicaciones robustas y mantenibles que pueden adaptarse a futuros cambios y requerimientos.
Patrón Singleton
El patrón Singleton es un patrón de diseño creacional que asegura que una clase tenga una única instancia en toda la aplicación y proporciona un punto de acceso global a ella. En PHP, este patrón es útil para manejar recursos compartidos, como conexiones a bases de datos, gestores de configuración o registros de logs, evitando la creación de múltiples instancias que podrían generar inconsistencias o consumo innecesario de recursos.
Para implementar un Singleton en PHP, se deben seguir ciertos pasos clave:
- Declarar el constructor como privado para evitar que se puedan crear instancias desde fuera de la clase.
- Utilizar una propiedad estática que almacene la instancia única de la clase.
- Proporcionar un método público y estático que permita obtener la instancia única, creando la instancia si aún no existe.
- Prevenir la clonación y deserialización del objeto, declarando los métodos mágicos
__clone
y__wakeup
como privados o lanzando una excepción en su interior.
A continuación, se presenta un ejemplo de cómo implementar el patrón Singleton en PHP:
<?php
class BaseDeDatos
{
private static $instancia;
private $conexion;
private function __construct()
{
$dsn = 'mysql:host=localhost;dbname=mi_base_de_datos;charset=utf8mb4';
$usuario = 'mi_usuario';
$contraseña = 'mi_contraseña';
try {
$this->conexion = new PDO($dsn, $usuario, $contraseña);
$this->conexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo "Error de conexión: " . $e->getMessage() . "\n";
exit;
}
}
public static function obtenerInstancia(): BaseDeDatos
{
if (!isset(self::$instancia)) {
self::$instancia = new self();
}
return self::$instancia;
}
public function obtenerConexion(): PDO
{
return $this->conexion;
}
private function __clone()
{
// Prevenir la clonación del objeto Singleton
}
private function __wakeup()
{
// Prevenir la deserialización del objeto Singleton
}
}
En este ejemplo, la clase BaseDeDatos
garantiza que solo haya una instancia de conexión con la base de datos:
- El constructor privado impide la creación directa de instancias mediante
new
. - La propiedad estática
$instancia
almacena la única instancia de la clase. - El método público
obtenerInstancia
permite acceder a la instancia única, creando una nueva si aún no existe. - Los métodos
__clone
y__wakeup
están declarados como privados para evitar la clonación y deserialización del objeto, manteniendo la unicidad.
Para utilizar esta clase en la aplicación:
<?php
// Obtener la instancia única de la conexión a la base de datos
$bd = BaseDeDatos::obtenerInstancia();
$conexion = $bd->obtenerConexion();
try {
$consulta = $conexion->query('SELECT * FROM usuarios');
$usuarios = $consulta->fetchAll(PDO::FETCH_ASSOC);
foreach ($usuarios as $usuario) {
echo "Usuario: " . $usuario['nombre'] . "\n";
}
} catch (PDOException $e) {
echo "Error en la consulta: " . $e->getMessage() . "\n";
}
Al utilizar el método obtenerInstancia
, nos aseguramos de que toda la aplicación comparta la misma conexión a la base de datos, evitando conexiones innecesarias y mejorando el rendimiento.
Es esencial considerar las ventajas del patrón Singleton:
- Consistencia: Garantiza que todos los componentes de la aplicación utilicen la misma instancia compartida.
- Control de acceso: Centraliza y controla el acceso a recursos críticos.
- Ahorro de recursos: Evita la sobrecarga de crear múltiples instancias de objetos pesados.
Sin embargo, también es importante tener en cuenta las desventajas y precauciones al implementar este patrón:
- Dificultad en pruebas unitarias: Al tener una instancia global, puede ser complicado aislar y mockear la clase durante las pruebas.
- Acoplamiento global: Fomenta el uso de variables globales, lo que puede dificultar el mantenimiento y escalabilidad.
- Problemas en entornos concurrentes: En aplicaciones multihilo, es necesario implementar mecanismos para garantizar la seguridad en hilos (thread safety).
Para mitigar algunos de estos problemas, se recomienda:
- Utilizar el Singleton con moderación y solo cuando sea realmente necesario.
- Considerar la inyección de dependencias como alternativa para gestionar las instancias y facilitar las pruebas.
- Asegurar que el Singleton sea inmutable o implementar mecanismos de sincronización si se espera acceso concurrente.
En versiones más recientes de PHP, es posible mejorar la implementación utilizando características modernas del lenguaje. Por ejemplo, se puede emplear la palabra clave static
en métodos para permitir la extensibilidad en clases derivadas:
<?php
class Registro
{
protected static $instancia;
protected $datos = [];
protected function __construct()
{
// Inicialización
}
public static function obtenerInstancia(): static
{
if (!isset(static::$instancia)) {
static::$instancia = new static();
}
return static::$instancia;
}
public function establecer(string $clave, $valor): void
{
$this->datos[$clave] = $valor;
}
public function obtener(string $clave)
{
return $this->datos[$clave] ?? null;
}
}
Con esta implementación, si se crea una clase que herede de Registro
, el método obtenerInstancia
devolverá una instancia única de la clase hija, lo que aporta flexibilidad.
Es importante destacar que, al utilizar Singletons, se debe prestar atención a las prácticas de seguridad:
- Proteger los métodos y propiedades sensibles con los modificadores de acceso adecuados.
- Asegurar que el Singleton no se convierta en un punto único de fallo o en un cuello de botella.
- Evitar exponer información sensible a través de la instancia global.
Patrón Factory
El patrón Factory es un patrón de diseño creacional que proporciona una interfaz para crear objetos sin exponer la lógica de instanciación al código cliente. Permite que las clases utilicen una clase Factory para crear objetos, promoviendo la desacoplamiento y facilitando la extensibilidad del sistema.
En PHP, el patrón Factory es útil cuando el código necesita trabajar con objetos que comparten una interfaz o clase base común, pero cuya clase concreta puede variar en tiempo de ejecución. Esto permite que el código sea más flexible y mantenible, ya que añadir nuevos tipos de objetos no requiere cambios significativos en el código existente.
Por ejemplo, consideremos una aplicación que genera informes en diferentes formatos: PDF, Excel y CSV. Cada formato tiene una implementación específica, pero todos comparten una interfaz común que define el método generar
.
Primero, definimos la interfaz Informe
:
<?php
interface Informe
{
public function generar(array $datos): void;
}
Luego, creamos las clases concretas que implementan esta interfaz:
<?php
class InformePDF implements Informe
{
public function generar(array $datos): void
{
echo "Generando informe en PDF con los datos proporcionados.\n";
}
}
class InformeExcel implements Informe
{
public function generar(array $datos): void
{
echo "Generando informe en Excel con los datos proporcionados.\n";
}
}
class InformeCSV implements Informe
{
public function generar(array $datos): void
{
echo "Generando informe en CSV con los datos proporcionados.\n";
}
}
Para encapsular la lógica de creación de estos objetos, implementamos una clase Factory:
<?php
class InformeFactory {
public static function crear(string $tipo): Informe {
return match (strtolower($tipo)) {
'pdf' => new InformePDF(),
'excel' => new InformeExcel(),
'csv' => new InformeCSV(),
default => throw new InvalidArgumentException("Tipo de informe no soportado: $tipo"),
};
}
}
En este ejemplo, el método crear
utiliza el operador match
para devolver una instancia del informe solicitado. Si el tipo no es reconocido, se lanza una excepción InvalidArgumentException
. Esta clase Factory centraliza la lógica de creación y permite al código cliente obtener instancias de informes sin conocer sus clases concretas.
El código cliente puede utilizar la Factory de la siguiente manera:
<?php
$tipoInforme = 'PDF'; // Este valor podría venir de la entrada del usuario o configuración
try {
$informe = InformeFactory::crear($tipoInforme);
$datos = ['ventas' => 1000, 'beneficios' => 200];
$informe->generar($datos);
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
En este caso, el cliente no necesita saber cómo se crean los informes ni instanciar las clases concretas. Simplemente solicita un informe del tipo deseado y lo utiliza a través de la interfaz Informe
.
Beneficios del patrón factory:
- Flexibilidad: Permite añadir nuevos tipos de productos sin modificar el código cliente.
- Desacoplamiento: El cliente depende de interfaces o clases abstractas, no de implementaciones concretas.
- Mantenibilidad: Centraliza la lógica de creación, facilitando cambios y actualizaciones.
Extensión del patrón con parámetros adicionales:
El patrón Factory también puede manejar parámetros adicionales necesarios para la creación de objetos. Por ejemplo, si necesitamos configurar propiedades específicas al crear un informe:
<?php
class InformeFactory
{
public static function crear(string $tipo, array $config): Informe
{
return match (strtolower($tipo)) {
'pdf' => new InformePDF($config),
'excel' => new InformeExcel($config),
'csv' => new InformeCSV($config),
default => throw new InvalidArgumentException("Tipo de informe no soportado: $tipo"),
};
}
}
Y las clases concretas podrían aceptar estos parámetros en su constructor:
<?php
class InformePDF implements Informe
{
private array $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function generar(array $datos): void
{
echo "Generando informe en PDF con configuración personalizada.\n";
}
}
Esta flexibilidad permite que la Factory adapte la creación de objetos a necesidades específicas, manteniendo el código cliente simple y desacoplado.
Variaciones del patrón factory en PHP:
- Factory Estática: Utiliza métodos estáticos para crear objetos, como en los ejemplos anteriores.
- Factory de Instancia: La Factory es instanciada, lo que permite mantener estado o configuraciones específicas.
Ejemplo de factory de instancia:
<?php
class InformeFactory
{
private array $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function crear(string $tipo): Informe
{
return match (strtolower($tipo)) {
'pdf' => new InformePDF($this->config),
'excel' => new InformeExcel($this->config),
'csv' => new InformeCSV($this->config),
default => throw new InvalidArgumentException("Tipo de informe no soportado: $tipo"),
};
}
}
// Uso de la Factory de instancia
$config = ['autor' => 'Juan Pérez', 'fecha' => '2024-05-01'];
$factory = new InformeFactory($config);
$informe = $factory->crear('excel');
$informe->generar($datos);
Esta aproximación permite que la Factory mantenga un estado interno, lo que puede ser útil si los objetos creados necesitan compartir alguna configuración común.
Integración con autoloading y namespaces:
En aplicaciones más grandes, es común utilizar autoloading y namespaces para organizar las clases. El patrón Factory puede aprovechar estas características para instanciar clases dinámicamente:
<?php
namespace App\Factories;
use App\Informes\Informe;
class InformeFactory
{
public static function crear(string $tipo): Informe
{
$clase = 'App\\Informes\\Informe' . ucfirst(strtolower($tipo));
if (class_exists($clase)) {
return new $clase();
}
throw new InvalidArgumentException("Clase $clase no encontrada.");
}
}
Con esta implementación, la Factory construye el nombre de la clase en base al tipo solicitado y utiliza el autoloading de PHP para cargarla. Es importante manejar correctamente namespaces y nombres de clases para evitar conflictos y errores.
Uso de factory con dependencias:
Si las clases concretas requieren inyecciones de dependencias, la Factory puede gestionar esto:
<?php
class InformeFactory
{
private Logger $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function crear(string $tipo): Informe
{
return match (strtolower($tipo)) {
'pdf' => new InformePDF($this->logger),
'excel' => new InformeExcel($this->logger),
'csv' => new InformeCSV($this->logger),
default => throw new InvalidArgumentException("Tipo de informe no soportado: $tipo"),
};
}
}
Aquí, el logger se inyecta en la Factory y luego se pasa a los objetos creados, asegurando que todas las instancias compartan la misma dependencia.
Consideraciones de diseño:
- Simplicidad: Mantener la Factory simple y enfocada en la creación de objetos.
- Responsabilidad Única: La Factory debe tener una única responsabilidad: instanciar objetos.
- Evolución: Si la lógica de creación se vuelve compleja, considerar refinar la Factory o utilizar otros patrones como Abstract Factory o Builder.
Cuando evitar el patrón factory:
- Si la creación de objetos es sencilla y no es probable que cambie, el uso de una Factory puede ser innecesario.
- Evitar introducir complejidad adicional si el sistema no lo requiere.
Patrón Strategy
El patrón Strategy es un patrón de diseño de comportamiento que permite definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables en tiempo de ejecución. Este patrón facilita la separación de responsabilidades, permitiendo que el algoritmo varíe independientemente de los clientes que lo utilizan, lo cual promueve un código más flexible y mantenible.
En PHP, el patrón Strategy es útil cuando una clase tiene múltiples variantes de un algoritmo y necesita cambiar su comportamiento de forma dinámica. En lugar de utilizar múltiples estructuras condicionales para seleccionar el algoritmo apropiado, se puede delegar esta responsabilidad a objetos encapsulados que representan cada estrategia.
Para ilustrar la implementación del patrón Strategy, consideremos un ejemplo de cálculo de impuestos para diferentes países. Supongamos que queremos calcular el impuesto sobre las ventas en función del país donde se realiza la transacción.
Primero, definimos una interfaz que declara el método común para todas las estrategias de cálculo de impuestos:
<?php
interface EstrategiaImpuesto
{
public function calcular(float $cantidad): float;
}
A continuación, creamos clases concretas que implementan esta interfaz para cada país:
<?php
class ImpuestoEspaña implements EstrategiaImpuesto
{
public function calcular(float $cantidad): float
{
return $cantidad * 0.21; // IVA del 21%
}
}
class ImpuestoFrancia implements EstrategiaImpuesto
{
public function calcular(float $cantidad): float
{
return $cantidad * 0.20; // TVA del 20%
}
}
class ImpuestoAlemania implements EstrategiaImpuesto
{
public function calcular(float $cantidad): float
{
return $cantidad * 0.19; // MwSt del 19%
}
}
Cada clase representa una estrategia de cálculo de impuestos específica para un país, implementando el método calcular
según las normas fiscales correspondientes.
Luego, definimos una clase Contexto que utilizará una estrategia de impuesto:
<?php
class CalculadoraImpuestos
{
private EstrategiaImpuesto $estrategia;
public function __construct(EstrategiaImpuesto $estrategia)
{
$this->estrategia = $estrategia;
}
public function establecerEstrategia(EstrategiaImpuesto $estrategia): void
{
$this->estrategia = $estrategia;
}
public function calcularImpuesto(float $cantidad): float
{
return $this->estrategia->calcular($cantidad);
}
}
La clase CalculadoraImpuestos
mantiene una referencia a una instancia de EstrategiaImpuesto
y delega el cálculo del impuesto a esta estrategia. De esta forma, es posible cambiar la estrategia de cálculo en tiempo de ejecución según sea necesario.
El código cliente puede utilizar la calculadora de impuestos de la siguiente manera:
<?php
$cantidad = 100.00; // Cantidad sobre la que se calculará el impuesto
$calculadora = new CalculadoraImpuestos(new ImpuestoEspaña());
$impuestoEspaña = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en España: €" . $impuestoEspaña . "\n";
$calculadora->establecerEstrategia(new ImpuestoFrancia());
$impuestoFrancia = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en Francia: €" . $impuestoFrancia . "\n";
$calculadora->establecerEstrategia(new ImpuestoAlemania());
$impuestoAlemania = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en Alemania: €" . $impuestoAlemania . "\n";
En este ejemplo, se crea una instancia de CalculadoraImpuestos
con la estrategia inicial ImpuestoEspaña
. Posteriormente, se cambian las estrategias a ImpuestoFrancia
e ImpuestoAlemania
según sea necesario, sin modificar la lógica interna de la calculadora. Esto demuestra la capacidad del patrón Strategy para cambiar el comportamiento de un objeto en tiempo de ejecución.
Ventajas del patrón Strategy:
- Desacoplamiento: Separa las clases que utilizan los algoritmos de las que los implementan, reduciendo la dependencia entre ellas.
- Reutilización: Las estrategias pueden ser reutilizadas por diferentes contextos y son fáciles de mantener y extender.
- Sustitución: Permite añadir o cambiar estrategias sin modificar el código de los clientes, facilitando la extensibilidad.
Implementación con clases anónimas:
En PHP 7 o superior, es posible utilizar clases anónimas para definir estrategias de manera más concisa:
<?php
$impuestoItalia = new class implements EstrategiaImpuesto {
public function calcular(float $cantidad): float {
return $cantidad * 0.22; // IVA del 22%
}
};
$calculadora->establecerEstrategia($impuestoItalia);
$impuestoItalia = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en Italia: €" . $impuestoItalia . "\n";
Esta forma de implementar estrategias puede ser útil cuando se necesita una estrategia específica sin crear una clase dedicada.
Uso de funciones anónimas como estrategias:
Alternativamente, se pueden utilizar funciones anónimas o closures para representar estrategias simples:
<?php
class CalculadoraImpuestos
{
private $estrategia;
public function __construct(callable $estrategia)
{
$this->estrategia = $estrategia;
}
public function establecerEstrategia(callable $estrategia): void
{
$this->estrategia = $estrategia;
}
public function calcularImpuesto(float $cantidad): float
{
return call_user_func($this->estrategia, $cantidad);
}
}
$impuestoPortugal = function (float $cantidad): float {
return $cantidad * 0.23; // IVA del 23%
};
$calculadora->establecerEstrategia($impuestoPortugal);
$impuestoPortugal = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en Portugal: €" . $impuestoPortugal . "\n";
Este enfoque es práctico cuando las estrategias son sencillas y no se requiere una clase completa para cada una.
Integración con el patrón Factory:
El patrón Strategy a menudo se combina con el patrón Factory para crear estrategias de forma dinámica basadas en cierta configuración o entrada del usuario. A continuación, se muestra un ejemplo de cómo integrar ambos patrones:
<?php
class EstrategiaImpuestoFactory
{
public static function crear(string $pais): EstrategiaImpuesto
{
return match (strtolower($pais)) {
'españa' => new ImpuestoEspaña(),
'francia' => new ImpuestoFrancia(),
'alemania' => new ImpuestoAlemania(),
'italia' => new class implements EstrategiaImpuesto {
public function calcular(float $cantidad): float
{
return $cantidad * 0.22;
}
},
default => throw new InvalidArgumentException("País no soportado: $pais"),
};
}
}
// Uso
$pais = 'Italia';
try {
$estrategia = EstrategiaImpuestoFactory::crear($pais);
$calculadora->establecerEstrategia($estrategia);
$impuesto = $calculadora->calcularImpuesto($cantidad);
echo "Impuesto en $pais: €" . $impuesto . "\n";
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Este enfoque permite que el código sea más escalable, ya que agregar soporte para nuevos países solo implica añadir una nueva estrategia en la fábrica.
Consideraciones al utilizar el patrón Strategy:
- Simplicidad: Evita complejas estructuras condicionales y simplifica el código.
- Cohesión: Cada estrategia se enfoca en un algoritmo específico, promoviendo una alta cohesión de las clases.
- Bajo acoplamiento: El contexto y las estrategias están desacoplados, lo que facilita las pruebas y el mantenimiento.
Posibles desventajas:
- Sobrecarga de clases: Para casos simples, la creación de múltiples clases puede parecer excesiva.
- Complejidad: Añade un nivel adicional de abstracción que debe ser justificado por la necesidad de flexibilidad.
Aplicación en sistemas reales:
El patrón Strategy es ampliamente utilizado en sistemas donde se necesita seleccionar dinámicamente algoritmos o comportamientos, como:
- Sistemas de autenticación: Donde se puede cambiar el método de autenticación (token, contraseña, OAuth).
- Procesamiento de pagos: Diferentes pasarelas de pago pueden ser implementadas como estrategias.
- Compresión de datos: Selección del algoritmo de compresión en función de las necesidades.
Ejemplo de un sistema de autenticación:
<?php
interface EstrategiaAutenticacion
{
public function autenticar(string $usuario, string $clave): bool;
}
class AutenticacionLocal implements EstrategiaAutenticacion
{
public function autenticar(string $usuario, string $clave): bool
{
// Lógica de autenticación local
return true;
}
}
class AutenticacionLDAP implements EstrategiaAutenticacion
{
public function autenticar(string $usuario, string $clave): bool
{
// Lógica de autenticación LDAP
return true;
}
}
class GestorAutenticacion
{
private EstrategiaAutenticacion $estrategia;
public function __construct(EstrategiaAutenticacion $estrategia)
{
$this->estrategia = $estrategia;
}
public function autenticarUsuario(string $usuario, string $clave): bool
{
return $this->estrategia->autenticar($usuario, $clave);
}
}
// Uso
$gestor = new GestorAutenticacion(new AutenticacionLocal());
$autenticado = $gestor->autenticarUsuario('usuario', 'contraseña');
if ($autenticado) {
echo "Usuario autenticado correctamente.\n";
} else {
echo "Error en la autenticación.\n";
}
En este ejemplo, el gestor de autenticación puede cambiar fácilmente entre diferentes estrategias sin modificar su código interno, lo que permite adaptarse a diferentes entornos o requisitos.
Patrón Observer
El patrón Observer es un patrón de diseño de comportamiento que define una relación uno a muchos entre objetos, de manera que cuando un objeto sujeto cambia de estado, notifica automáticamente a sus observadores dependientes. Este patrón es esencial para implementar sistemas con componentes que necesitan ser informados de cambios relevantes de forma desacoplada y reactiva.
En PHP, el patrón Observer es útil para crear mecanismos de suscripción y notificación, permitiendo que los objetos se comuniquen sin conocer las implementaciones específicas de los demás. Esto promueve un diseño más flexible y facilita la extensibilidad de la aplicación.
Para implementar el patrón Observer en PHP, se siguen generalmente los siguientes pasos:
Definir una interfaz para los observadores que declare el método de actualización que recibirán las notificaciones.
Implementar la clase sujeto que mantiene una lista de observadores y proporciona métodos para agregar y eliminar observadores. Además, incluye el método para notificar a los observadores de los cambios.
Crear las clases observadoras que implementan la interfaz de los observadores y definen las acciones a realizar cuando reciben una notificación.
A continuación, presentamos un ejemplo práctico donde implementamos el patrón Observer en un sistema de comercio electrónico que necesita notificar a varios servicios cuando se registra un nuevo usuario.
Definir la interfaz del Observador:
<?php
interface Observador
{
public function actualizar(string $evento, $datos): void;
}
Implementar la clase Sujeto:
<?php
class RegistroUsuarios
{
private array $observadores = [];
public function agregarObservador(Observador $observador): void
{
$this->observadores[] = $observador;
}
public function eliminarObservador(Observador $observador): void
{
$indice = array_search($observador, $this->observadores, true);
if ($indice !== false) {
unset($this->observadores[$indice]);
}
}
public function registrarUsuario(array $datosUsuario): void
{
// Lógica para registrar al usuario
echo "Usuario registrado con email: " . $datosUsuario['email'] . "\n";
$this->notificar('usuario_registrado', $datosUsuario);
}
private function notificar(string $evento, $datos): void
{
foreach ($this->observadores as $observador) {
$observador->actualizar($evento, $datos);
}
}
}
En esta clase RegistroUsuarios
, después de registrar al usuario, se llama al método notificar
para informar a todos los observadores sobre el evento ocurrido.
Crear las clases Observadoras:
<?php
class EnviarEmailBienvenida implements Observador
{
public function actualizar(string $evento, $datos): void
{
if ($evento === 'usuario_registrado') {
echo "Enviando email de bienvenida a: " . $datos['email'] . "\n";
}
}
}
class AsignarDescuentoInicial implements Observador
{
public function actualizar(string $evento, $datos): void
{
if ($evento === 'usuario_registrado') {
echo "Asignando descuento inicial al usuario ID: " . $datos['id'] . "\n";
}
}
}
class ActualizarEstadisticas implements Observador
{
public function actualizar(string $evento, $datos): void
{
if ($evento === 'usuario_registrado') {
echo "Actualizando estadísticas de usuarios registrados.\n";
}
}
}
Cada observador implementa la acción específica que debe llevar a cabo cuando se produce el evento usuario_registrado
.
Utilizar el patrón Observer en el código:
<?php
$registro = new RegistroUsuarios();
// Agregar observadores
$registro->agregarObservador(new EnviarEmailBienvenida());
$registro->agregarObservador(new AsignarDescuentoInicial());
$registro->agregarObservador(new ActualizarEstadisticas());
// Datos del nuevo usuario
$datosUsuario = [
'id' => 1,
'nombre' => 'María López',
'email' => 'maria.lopez@example.com'
];
// Registrar al usuario
$registro->registrarUsuario($datosUsuario);
La salida resultante sería:
Usuario registrado con email: maria.lopez@example.com
Enviando email de bienvenida a: maria.lopez@example.com
Asignando descuento inicial al usuario ID: 1
Actualizando estadísticas de usuarios registrados.
En este ejemplo, el sujeto RegistroUsuarios
notifica a todos los observadores interesados cuando se registra un nuevo usuario, sin necesidad de conocer sus implementaciones concretas. Esto garantiza un bajo acoplamiento y facilita la adición o eliminación de funcionalidad sin afectar al código existente.
Ventajas del patrón Observer:
- Desacoplamiento: Los sujetos y observadores están desacoplados, lo que mejora la mantenibilidad del código.
- Reutilización: Los observadores pueden ser reutilizados con diferentes sujetos o en distintos contextos.
- Flexibilidad: Es fácil añadir nuevos observadores o modificar los existentes sin alterar el sujeto.
Uso de las interfaces SplSubject y SplObserver de PHP:
PHP proporciona las interfaces SplSubject y SplObserver dentro de la librería estándar SPL, que pueden facilitar la implementación del patrón Observer.
Implementar el sujeto utilizando SplSubject:
<?php
class RegistroUsuarios implements SplSubject {
private array $observadores;
private array $datosUsuario;
public function __construct() {
$this->observadores = [];
}
public function attach(SplObserver $observador): void {
$this->observadores[] = $observador;
}
public function detach(SplObserver $observador): void {
$indice = array_search($observador, $this->observadores, true);
if ($indice !== false) {
unset($this->observadores[$indice]);
}
}
public function notify(): void {
foreach ($this->observadores as $observador) {
$observador->update($this);
}
}
public function registrarUsuario(array $datosUsuario): void {
$this->datosUsuario = $datosUsuario;
echo "Usuario registrado con email: " . $datosUsuario['email'] . "\n";
$this->notify();
}
public function getDatosUsuario(): array {
return $this->datosUsuario;
}
}
Implementar los observadores utilizando SplObserver:
<?php
class EnviarEmailBienvenida implements SplObserver
{
public function update(SplSubject $sujeto): void
{
$datos = $sujeto->getDatosUsuario();
echo "Enviando email de bienvenida a: " . $datos['email'] . "\n";
}
}
class AsignarDescuentoInicial implements SplObserver
{
public function update(SplSubject $sujeto): void
{
$datos = $sujeto->getDatosUsuario();
echo "Asignando descuento inicial al usuario ID: " . $datos['id'] . "\n";
}
}
class ActualizarEstadisticas implements SplObserver
{
public function update(SplSubject $sujeto): void
{
echo "Actualizando estadísticas de usuarios registrados.\n";
}
}
Utilizar el patrón Observer con las interfaces SPL:
<?php
$registro = new RegistroUsuarios();
// Agregar observadores
$registro->attach(new EnviarEmailBienvenida());
$registro->attach(new AsignarDescuentoInicial());
$registro->attach(new ActualizarEstadisticas());
// Datos del nuevo usuario
$datosUsuario = [
'id' => 2,
'nombre' => 'Carlos Sánchez',
'email' => 'carlos.sanchez@example.com'
];
// Registrar al usuario
$registro->registrarUsuario($datosUsuario);
La salida será similar, demostrando que el uso de las interfaces SPL simplifica la estructura y mejora la compatibilidad con las funcionalidades nativas de PHP.
Aplicaciones comunes del patrón Observer:
- Sistemas de eventos: Implementación de mecanismos de publicación y suscripción para manejar eventos.
- Interfaces de usuario: Actualización automática de vistas o componentes cuando cambia el modelo de datos.
- Notificaciones: Envío de alertas, correos electrónicos o mensajes cuando ocurren ciertos eventos.
- Registro y auditoría: Seguimiento de acciones o cambios en el sistema para fines de auditoría.
Consideraciones al implementar el patrón Observer:
- Gestión de dependencias: Asegurar que los observadores no introduzcan dependencias cíclicas o fuertes con el sujeto.
- Rendimiento: En sistemas con muchos observadores o eventos frecuentes, optimizar el proceso de notificación para evitar impactos en el rendimiento.
- Error handling: Manejar adecuadamente las excepciones en los observadores para prevenir que un error en uno afecte a los demás.
Mejoras avanzadas:
Para mejorar la modularidad y control de notificaciones, podríamos implementar un sistema donde los observadores se suscriben a eventos específicos.
Implementación de un sistema de eventos:
<?php
class RegistroUsuarios
{
private array $observadores = [];
private array $datosUsuario;
public function suscribir(string $evento, callable $observador): void
{
$this->observadores[$evento][] = $observador;
}
public function desuscribir(string $evento, callable $observador): void
{
if (isset($this->observadores[$evento])) {
$indice = array_search($observador, $this->observadores[$evento], true);
if ($indice !== false) {
unset($this->observadores[$evento][$indice]);
}
}
}
private function notificar(string $evento, $datos): void
{
if (isset($this->observadores[$evento])) {
foreach ($this->observadores[$evento] as $observador) {
call_user_func($observador, $datos);
}
}
}
public function registrarUsuario(array $datosUsuario): void
{
$this->datosUsuario = $datosUsuario;
echo "Usuario registrado con email: " . $datosUsuario['email'] . "\n";
$this->notificar('usuario_registrado', $datosUsuario);
}
}
Suscribir observadores a eventos específicos:
<?php
$registro = new RegistroUsuarios();
$registro->suscribir('usuario_registrado', function ($datos) {
echo "Enviando email de bienvenida a: " . $datos['email'] . "\n";
});
$registro->suscribir('usuario_registrado', function ($datos) {
echo "Asignando descuento inicial al usuario ID: " . $datos['id'] . "\n";
});
$registro->suscribir('usuario_registrado', function () {
echo "Actualizando estadísticas de usuarios registrados.\n";
});
// Registrar al usuario
$datosUsuario = [
'id' => 3,
'nombre' => 'Laura González',
'email' => 'laura.gonzalez@example.com'
];
$registro->registrarUsuario($datosUsuario);
Este enfoque utiliza funciones anónimas como observadores, lo que puede simplificar el código en casos donde no se requiere una clase completa para cada observador.
Todas las lecciones de PHP
Accede a todas las lecciones de PHP y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Php
Introducción Y Entorno
Instalación Y Primer Programa De Php
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Estructuras De Control
Sintaxis
Funciones Y Llamada De Funciones
Sintaxis
Cadenas De Texto Y Manipulación
Sintaxis
Manejo De Números
Sintaxis
Manejo De Fechas Y Tiempo
Sintaxis
Manejo De Arrays
Sintaxis
Introducción A La Poo En Php
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Constructores Y Destructores
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Traits
Programación Orientada A Objetos
Namespaces
Programación Orientada A Objetos
Autoloading De Clases
Programación Orientada A Objetos
Manejo De Errores Y Excepciones
Programación Orientada A Objetos
Manejo De Archivos
Programación Orientada A Objetos
Patrones De Diseño
Programación Orientada A Objetos
Introducción A Los Formularios En Php
Formularios
Procesamiento De Datos De Formularios
Formularios
Manejo De Archivos En Formularios
Formularios
Redirecciones Y Retroalimentación Al Usuario
Formularios
Formularios Dinámicos Y Separación De Lógica
Formularios
Introducción A La Persistencia En Php
Persistencia
Conexión A Bases De Datos
Persistencia
Consultas Y Operaciones Crud
Persistencia
Gestión De Transacciones
Persistencia
Manejo De Errores Y Excepciones En Base De Datos
Persistencia
Patrones De Acceso A Datos
Persistencia
Concepto De Sesiones En Php
Sesiones Y Cookies
Configuración De Sesiones
Sesiones Y Cookies
Cookies
Sesiones Y Cookies
Manejo Avanzado De Sesiones Y Cookies
Sesiones Y Cookies
Principales Vulnerabilidades En Php
Seguridad
Seguridad En Formularios Y Entrada De Datos
Seguridad
Protección Frente A Inyección Sql
Seguridad
Gestión De Contraseñas Y Cifrado
Seguridad
Seguridad En Sesiones Y Cookies
Seguridad
Configuraciones De Php Para Seguridad
Seguridad
Introducción Al Testing En Php
Testing
Phpunit
Testing
Cobertura De Código En Testing
Testing
Test Doubles (Mocks, Stubs, Fakes, Spies)
Testing
Pruebas De Integración Y Funcionales
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender la importancia de los patrones de diseño en el desarrollo de software.
- Identificar las principales categorías de patrones: creacionales, estructurales y de comportamiento.
- Implementar patrones básicos como Singleton, Factory, Strategy y Observer en PHP.
- Mejorar la mantenibilidad y escalabilidad del código usando patrones de diseño.