Patrones de Comportamiento en PHP

Guía Completa sobre los Patrones de Comportamiento en PHP

Bienvenidos a esta clase sobre los Patrones de Comportamiento en PHP. En esta lección aprenderemos qué son los patrones de comportamiento, su importancia en el desarrollo de software, y estudiaremos en profundidad los patrones de comportamiento más utilizados en PHP.

Los patrones de comportamiento son patrones de diseño software que nos permiten definir soluciones reutilizables a problemas comunes de comunicación y asignación de responsabilidades entre objetos. Nos ayudan a desacoplar las clases y a crear aplicaciones PHP más flexibles y escalables.

Al finalizar esta lección tendrás una sólida base sobre los patrones de comportamiento en PHP y podrás aplicarlos para crear aplicaciones más flexibles y mantenibles. ¡Comencemos!

Introducción a los Patrones de Comportamiento en PHP

Los patrones de comportamiento se enfocan en mejorar la comunicación entre clases y la distribución de responsabilidades. Nos permiten desacoplar las clases entre sí, haciendo que el programa sea más flexible y fácil de mantener.

Definición de patrones de comportamiento

Los patrones de comportamiento son patrones de diseño software que solucionan problemas de comunicación entre objetos y asignación de responsabilidades. Estos patrones encapsulan en una entidad un comportamiento concreto, de forma que ese comportamiento sea configurable e independiente del resto de la aplicación.

Algunas de las responsabilidades y problemas que resuelven son:

  • - Encapsular algoritmos en entidades propias para hacerlos intercambiables.

  • - Definir el flujo de un proceso.

  • - Propagar cambios de estado entre clases desacopladas.

  • - Especificar cómo se realizan ciertas acciones sin definir su implementación.

  • - Dar acceso secuencial a los elementos de una colección sin exponer su estructura interna.

Importancia en el desarrollo PHP

Los patrones de comportamiento son muy útiles en PHP porque nos permiten crear aplicaciones más flexibles, mantenibles y fáciles de extender. Algunos beneficios de aplicar estos patrones:

  • - Evitar acoplamiento entre clases: Los patrones de comportamiento desacoplan las clases permitiendo variar partes de la aplicación de manera independiente.

  • - Código reutilizable: Encapsulan comportamientos genéricos que podemos reutilizar.

  • - Código más legible y mantenible: Al dividir las responsabilidades en diferentes clases, el código es más fácil de entender y mantener.

  • -Extensibilidad: Podemos definir nuevos comportamientos sin modificar las clases existentes.

Lista de los principales patrones de comportamiento

Los patrones de comportamiento más utilizados en PHP son:

  • - Strategy: Permite definir una familia de algoritmos encapsulados y hacerlos intercambiables.

  • - Observer: Permite definir un mecanismo de suscripción para notificar a múltiples objetos sobre cambios acontecidos.

  • - State: Permite que un objeto modifique su comportamiento cada vez que cambie su estado interno.

  • - Command: Encapsula una operación en un objeto, permitiendo ejecutarla remotamente y deshacerla.

  • - Iterator: Permite recorrer los elementos de una colección secuencialmente sin exponer su implementación subyacente.

A continuación veremos en detalle la definición, características, ejemplos de uso y ventajas de estos importantes patrones de comportamiento en PHP.


Patrón Strategy para variar algoritmos en PHP

El patrón Strategy nos permite encapsular algoritmos en clases separadas e intercambiarlos entre sí dependiendo de nuestras necesidades. Es muy útil cuando tenemos diferentes variantes de un mismo algoritmo o cuando queremos delegar algún comportamiento a las subclases.

Definición y aplicaciones del patrón Strategy

El patrón Strategy implica definir una familia de algoritmos, encapsular cada uno de ellos y hacerlos intercambiables. Esto permite que el algoritmo varíe independientemente de los clientes que lo utilizan. En PHP, este patrón se usa comúnmente para manejar diferentes estrategias de procesamiento de datos.

El patrón Strategy se compone de:

  • - La interfaz Strategy: define los métodos comunes a todos los algoritmos intercambiables.

  • - Las clases ConcreteStrategy: implementan distintas variantes del algoritmo que siguen la interfaz Strategy.

  • - El contexto: utiliza una referencia a un objeto Strategy para ejecutar el algoritmo. Esto permite cambiar el algoritmo en tiempo de ejecución.

Este patrón es muy útil cuando:

  • - Existen diversas variantes de un algoritmo y queremos intercambiarlas dinámicamente.

  • - Queremos evitar condicionales para elegir el comportamiento deseado.

  • - Queremos aislar el algoritmo de los clientes que lo usan.

Ejemplo Práctico del patrón Strategy

Veamos un ejemplo en PHP de una tienda online que puede calcular el costo de envío de los pedidos mediante diferentes proveedores. Definiremos una interfaz ShippingStrategy con un método getCost(). Luego tendremos dos clases ConcreteStrategy que implementan distintas fórmulas para calcular el costo: NormalShipping y ExpressShipping.

El contexto Order usará una referencia a un objeto ShippingStrategy para calcular el costo de envío según el proveedor elegido:

<?php
// Interfaz Strategy
interface ShippingStrategy
{
    public function getCost(Order $order);
}

// ConcreteStrategy A
class NormalShipping implements ShippingStrategy
{

    public function getCost(Order $order)
    {
        return $order->getTotal() * 0.05;
    }
}

// ConcreteStrategy B
class ExpressShipping implements ShippingStrategy
{

    public function getCost(Order $order)
    {
        return $order->getTotal() * 0.1;
    }
}

// Contexto
class Order
{

    private $shippingStrategy;
    private $total;

    // Inyectamos la estrategia en el constructor
    public function __construct(ShippingStrategy $strategy)
    {
        $this->shippingStrategy = $strategy;
    }

    public function calculateTotal()
    {
        $cost = $this->shippingStrategy->getCost($this);
        return $this->total + $cost;
    }

    public function getTotal()
    {
        return $this->total;
    }

    public function setStrategy(ExpressShipping $express)
    {
        $this->shippingStrategy = $express;
    }
}

// Cliente
$order = new Order(new NormalShipping());
echo $order->calculateTotal(); // Usa NormalShipping

$order->setStrategy(new ExpressShipping());
echo $order->calculateTotal();// Ahora usa ExpressShipping

En este ejemplo vemos que podemos cambiar dinámicamente la estrategia utilizada por Order y calcular el costo de diferentes maneras. El Contexto Order está desacoplado de la implementación concreta de los algoritmos de envío.


Patrón Observer para desacoplar objetos en PHP

El patrón Observer permite definir un mecanismo de suscripción para notificar a múltiples objetos sobre cualquier evento que suceda al objeto que están observando.

Definición y aplicaciones del patrón Observer

El patrón Observer desacopla la clase que envía notificaciones de las clases que reciben esas notificaciones. De esta forma se pueden suscribir o cancelar la suscripción de observadores en tiempo de ejecución.

Los componentes que definen este patrón son:

  • - Subject: el objeto observado. Mantiene una lista de observadores y notifica los cambios a todos ellos.

  • - Observer: interfaz para los objetos que quieren recibir notificaciones. Define el método de actualización.

  • - ConcreteObserver: implementaciones concretas que reciben notificaciones y ejecutan acciones.

Las ventajas de utilizar el patrón Observer son:

  • - Desacoplamiento entre Subject y Observers.

  • - Permite suscribir y cancelar la suscripción de observadores en tiempo real.

  • - Facilita la propagación de cambios en estructuras de datos complejas.

  • - Open/closed principle: podemos añadir nuevos observadores sin modificar el Subject.

Ejemplo Práctico del patrón Observer

Veamos un ejemplo en PHP de un blog que notifica a los usuarios suscritos cuando se publica una entrada nueva.

El Subject será la clase Blog, que mantiene un array de suscriptores y notifica la actualización:

Los Observers serán los usuarios suscritos al Blog:

<?php

class Post
{
    // Aributos clase Post
}

/* SUBJECT */
class Blog
{

    private $observers = [];

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer)
    {
        // Código para eliminar el observer
    }

    public function notifyObservers()
    {
        // Notifica a todos los observers
    }

    public function addPost(Post $post)
    {
        // Lógica para añadir entrada

        $this->notifyObservers();
    }
}


/* OBSERVERS */
interface Observer
{
    public function update(Blog $blog);
}

class User implements Observer
{

    private $email;

    public function __construct($email)
    {
        $this->email = $email;
    }

    public function update(Blog $blog)
    {
        // Enviar email de notificación
    }
}

De esta forma los usuarios suscritos recibirán una notificación automática cuando se añada una entrada nueva al blog.


Patrón State para control de estados en PHP

El patrón State permite que un objeto modifique su comportamiento cada vez que cambia su estado interno. Parecido a una máquina de estados finitos.

¿En qué consiste el patrón State?

Este patrón consiste en definir diferentes comportamientos en clases separadas y cambiar el comportamiento del contexto delegando en el objeto estado correspondiente.

Permite aislar los comportamientos específicos de cada estado en clases independientes, evitando condicionales complejas.

Casos de uso del patrón State

El patrón State es muy útil para controlar flujos de trabajo, máquinas de estados y contextos donde el comportamiento depende del estado. Algunos ejemplos son:

  • - Estados de un proceso (pendiente, en curso, completado, cancelado, etc).

  • - Estados de un carrito de compras online.

  • - Estados de una conexión (conectado, desconectado, en espera, etc).

  • - Estados de un pedido (creado, pagado, enviado, entregado, etc).

Ejemplo Práctico del patrón State

Veamos un ejemplo en PHP de los estados de un pedido online:

<?php
// Estado interface
interface OrderState
{
    public function next(OrderContext $order);
}

// Estados concretos
class PendingState implements OrderState
{

    public function next(OrderContext $order)
    {
        // Pasar al estado PagoPendiente
    }
}

class PaymentPendingState implements OrderState
{

    public function next(OrderContext $order)
    {
        // Pasar al estado Enviado
    }
}

// Contexto
class OrderContext
{

    private $state;

    public function setState(OrderState $state)
    {
        $this->state = $state;
    }

    public function onPaymentSuccessful()
    {
        $this->state->next($this); // Cambia el estado
    }
}

De esta forma agregamos nuevos comportamientos encapsulados en los distintos estados, en lugar de usar condicionales en una misma clase.


Patrón Command para encapsular peticiones en PHP

El patrón Command encapsula una petición como un objeto, permitiendo parametrizar los clientes con diferentes solicitudes, encolar o registrar solicitudes y soportar operaciones que pueden hacerse deshacer.

Explicación del patrón Command

Este patrón consiste en encapsular la lógica de una acción en una clase propia que define una interfaz común para ejecutar dicha acción.

Esto permite:

  • - Parametrizar un objeto con una acción a ejecutar.

  • - Ejecutar acciones de forma diferida o remota.

  • - Almacenar una pila de acciones para deshacerlas (undo).

Casos de uso del patrón Command

Algunos ejemplos de uso del patrón Command en PHP:

  • - Colas de trabajos en segundo plano.

  • - Registrar cambios para implementar undo/redo.

  • - Transacciones (commit, rollback).

  • - Macros en aplicaciones ofimáticas.

  • - Manejar peticiones HTTP entrantes.

Ejemplo Práctico del patrón Command

Veamos una implementación sencilla en PHP de un historial de comandos para deshacer acciones:

<?php

interface Command
{
    public function execute();
    public function undo();
}

// Comando concreto
class CreateUserCommand implements Command
{

    public function __construct(User $user)
    {
        // ...
    }

    public function execute()
    {
        // Código para crear el usuario
    }

    public function undo()
    {
        // Código para eliminar el usuario
    }
}

// Invocador
class CommandInvoker
{

    private $commands = [];

    public function execute(Command $command)
    {
        $this->commands[] = $command;
        $command->execute();
    }

    public function undo()
    {
        $command = array_pop($this->commands);
        $command->undo();
    }
}

De esta forma encapsulamos en objetos Command las acciones a realizar, pudiendo ejecutarlas, registrarlas y deshacerlas de forma sencilla.


Patrón Iterator para recorridos en PHP

El patrón Iterator permite recorrer los elementos de una colección secuencialmente sin exponer su estructura interna.

Introducción al patrón Iterator

El patrón Iterator define una interfaz con métodos para recorrer una colección de forma estándar, como current(), next(), key(), valid(), etc.

Las clases ConcreteIterator implementan esta interfaz, manteniendo un puntero a la posición actual en la colección.

Esto permite iterar colecciones complejas de forma transparente, sin acoplar la estructura a los clientes.

Ejemplo Práctico del patrón Iterator

PHP incluye la interfaz Iterator e IteratorAggregate para estandarizar los recorridos:

<?php
interface Iterator extends Traversable
{

    public function current();
    public function next();
    public function key();
    public function valid();
    public function rewind();
}

interface IteratorAggregate extends Traversable
{
    public function getIterator();
}

Para implementar un Iterator en nuestra clase, implementamos la interfaz y hacemos que getIterator() devuelva una instancia de la clase ConcreteIterator:

class MiColeccion implements IteratorAggregate
{

    public $property1 = "Public property one";
    public $property2 = "Public property two";
    public $property3 = "Public property three";
    public $property4 = "";

    public function __construct()
    {
        $this->property4 = "last property";
    }

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this);
    }
}

class MiColeccionIterator implements Iterator
{

    private $position = 0;
    private $array = array(
        "firstelement",
        "secondelement",
        "lastelement",
    );

    public function __construct()
    {
        $this->position = 0;
    }

    public function rewind(): void
    {
        var_dump(__METHOD__);
        $this->position = 0;
    }

    #[\ReturnTypeWillChange]
    public function current()
    {
        var_dump(__METHOD__);
        return $this->array[$this->position];
    }

    #[\ReturnTypeWillChange]
    public function key()
    {
        var_dump(__METHOD__);
        return $this->position;
    }

    public function next(): void
    {
        var_dump(__METHOD__);
        ++$this->position;
    }

    public function valid(): bool
    {
        var_dump(__METHOD__);
        return isset($this->array[$this->position]);
    }
}
    public function getIterator();
}

Esto nos permite iterar la colección con foreach:

/* Esto nos permite iterar la colección con foreach: */
$coleccion = new MiColeccion();

foreach ($coleccion as $elemento) {
    // Itera utilizando el Iterator
}

Conclusión

En esta lección hemos visto una introducción a los patrones de comportamiento más utilizados en PHP:

  • - El patrón Strategy permite encapsular algoritmos en clases intercambiables.

  • - El patrón Observer define un mecanismo de suscripción para propagar cambios.

  • - El patrón State permite cambiar el comportamiento de un objeto según su estado.

  • - El patrón Command encapsula una acción en un objeto.

  • - El patrón Iterator estandariza la forma de recorrer colecciones.

Estos patrones nos permiten crear aplicaciones PHP más flexibles, mantenibles y escalables al desacoplar las responsabilidades en diferentes clases.

Algunas recomendaciones al usar patrones de comportamiento:

  • - No sobre-ingeniería, aplicarlos sólo si aportan flexibilidad.

  • - Combinarlos para obtener mayores beneficios.

  • - Favorecer composición frente a herencia.

  • - No forzar su uso si no resuelven el problema.

  • - Utilizar interfaces para desacoplar implementaciones.

Los patrones de comportamiento son una pieza clave en el desarrollo de software orientado a objetos con PHP. ¡Espero que esta lección te haya facilitado su comprensión y uso!


Ejercicios Resueltos

  • Ejercicio 1

    Vamos a implementar una función para enviar notificaciones por email, sms y slack mediante el patrón Observer.

    <?php
    // Interfaz Observer
    interface NotificationObserver
    {
        public function update(Notification $notification);
    }
    
    // Observadores concretos
    class EmailNotification implements NotificationObserver
    {
        public function update(Notification $notification)
        {
            // Enviar email
        }
    }
    
    class SmsNotification implements NotificationObserver
    {
        public function update(Notification $notification)
        {
            // Enviar SMS
        }
    }
    
    // Sujeto
    class Notification
    {
    
        protected $observers = [];
    
        public function attach(NotificationObserver $observer)
        {
            $this->observers[] = $observer;
        }
    
        public function notifyObservers()
        {
            // Notifica a los observadores
        }
    }
    
    // Cliente
    $notification = new Notification();
    
    $notification->attach(new EmailNotification());
    $notification->attach(new SmsNotification());
    
    $notification->notifyObservers();// Envía emails y SMS
  • Ejercicio 2

    Implementemos una función para generar reportes PDF y CSV mediante el patrón Strategy.

    <?php
    // Strategy interface
    interface ReportStrategy
    {
        public function export(Report $report);
    }
    
    class PdfStrategy implements ReportStrategy
    {
        public function export(Report $report)
        {
            // Generar PDF
        }
    }
    
    class CsvStrategy implements ReportStrategy
    {
        public function export(Report $report)
        {
            // Generar CSV
        }
    }
    
    // Contexto
    class Report
    {
    
        protected $strategy;
    
        public function setExportStrategy(ReportStrategy $strategy)
        {
            $this->strategy = $strategy;
        }
    
        public function export()
        {
            $this->strategy->export($this);
        }
    }
    
    // Cliente
    $report = new Report();
    $report->setExportStrategy(new PdfStrategy());
    $report->export(); // Exporta a PDF
    
    $report->setExportStrategy(new CsvStrategy());
    $report->export();// Ahora a CSV

Preguntas Frecuentes

Cuando necesitemos desacoplar objetos, encapsular algoritmos intercambiables o añadir responsabilidades a clases. Aportan flexibilidad.

Analizando requisitos como algoritmos intercambiables, propagación de cambios, changes de estado, etc. Cada patrón resuelve un problema distinto.

Sí, de hecho es común combinarlos para obtener mayores beneficios. Por ejemplo Strategy + State o Command + Memento.

Depende del caso, en aplicaciones muy sencillas podrían no aportar valor. Pero en la mayoría de casos medios o grandes sí mejoran el diseño.

Aplicándolos sólo donde se necesiten extensibilidad y flexibilidad futura. No forcémoslos si con soluciones más simples es suficiente.

Deja un Comentario

¡Bienvenido a nuestro sitio web!

Este sitio utiliza cookies para mejorar tu experiencia de navegación y proporcionar funciones adicionales relacionadas con el análisis de tráfico y el rendimiento del sitio. Política de cookies.