Un ejemplo de uso del patrón strategy en PHP

Home / Ejemplos / Un ejemplo de uso del patrón strategy en PHP

Hace poco, trabajando en una mejora para un sistema que desarrollé para un cliente de Leeway me pasó lo siguiente:

Una parte del trabajo de la aplicación era obtener información financiera de diferentes fuentes (básicamente se trataba de obtener precios históricos de bonos).

Existían diferentes fuentes de consulta debido a que la información no siempre estaba disponible en todos los sitios (más allá de no disponer de APIs, pero esa es otra historia).

El punto es que, en la primera versión de la aplicación (que obviamente estaba desarrollada sobre el framework Symfony), simplemente creamos un método dentro del Controlador:

private function fetchBondPrice($symbol, \DateTime $date)
{
    try {
        if ($price = $this->fetchBondPriceFromQuoteNet($symbol, $date)) {

            return $price;
        }

        if ($price = $this->fetchBondPriceFromMorningStar($symbol, $date)) {

            return $price;

        }
    } catch (Exception $e) {

    }

    return null;
}

Y las cosas funcionaron… pero desde el comienzo quedó pendiente este comentario en el código:

* @todo Refactor into a Strategy Pattern

El momento oportuno llegó cuando debimos alterar algunos aspectos de las búsquedas específicas y, para asegurarnos de que todo saliera bien, decidimos usar PHPUnit y ahí se hizo casi obvia la necesidad de refactorizar el código.

La primera vuelta del refactor nos trajo consigo una clase sencilla, un Service de Symfony llamado

BondPriceFetcherManager

Que, a priori no era mucho más que una forma de deslindar responsabilidad desde el controlador hacia una clase más específica… pero seguíamos teniendo un método que había que modificar si queríamos incorporar una nueva fuente de datos (Algo que muy probablemente se venía).

Por otro lado, fue evidente que algo andaba mal cuando tuvimos que cambiar los métodos

fetchBondPriceFromQuoteNet

y

fetchBondPriceFromMorningStar

De privados a públicos para poder hacer los tests…

Había llegado el momento de implementar el patrón Strategy.

 

Muy brevemente, lo que hicimos fue crear una clase para cada fuente de datos:

QuoteNetBondPriceFetcher

Y

MorningStarBondPriceFetcher

Ambas derivadas del nuevo

BondPriceFetcher

Que simplemente se ve así:

<?php
abstract class BondPriceFetcher
{
    /**
     * @param $symbol
     * @param \DateTime $date
     * @return mixed
     */
    abstract public function fetchPrice($symbol, \DateTime $date);
}

Ahora sí las cosas se ponen interesantes 🙂

Después queda una nueva clase genérica:

BondPriceFetcherManager

Que tiene un método

public function fetchBondPrice($symbol, \DateTime $date)
{
    $price = null;

    foreach ($this->getFetchers() as $fetcher ) {
        try {
            if ( $price = $fetcher->fetchPrice( $symbol, $date) ) {

                break;
            }
        } catch ( Exception $e ) {
            $this->logger->addDebug( 'Error buscando precio del bono: '.$symbol.'-'.$date->format('Y-m-d').' usando '.get_class($fetcher).': '.$e->getMessage() );
        }
    }

    return $price;
}

Obviamente, también tiene su método

public function addFetcher( BondPriceFetcher $fetcher )
{
    $this->fetchers[] = $fetcher;

    return $this;
}

Y

/**
 * @param Logger $logger
 * @return BondPriceFetcherManager
 */
public function setLogger(Logger $logger)
{
    $this->logger = $logger;
    
    return $this;
}

Y listo! Ya nos quedó una muy linda implementación que permite:

  1. Extender la cantidad de fuentes de datos (Basta con crear un nuevo BondPriceFetcher y agregar una nueva llamada a addFetcher)
  2. Alterar la prioridad que se le da a cada fuente (Basta con cambiar el orden de las llamadas a addFetcher) y sin alterar en absoluto el código propio del FetcherManager
  3. Testear automáticamente toda esta funcionalidad
  4. Reutilizar esta funcionalidad (Si se hiciera en un Bundle por ejemplo)

Debo reconocer que, si bien no soy un gran fanático de los patrones de diseño, Strategy es definitivamente mi favorito 🙂

El mayor aporte del patrón Strategy es el desacoplamiento (además de generar un código más elegante a fuerza de un poco más de texto)

¿Cómo fue tu experiencia con la aplicación de patrones de diseño?

mchojrin

CEO at Leeway
Mauro es Lic. en Ciencias de la Computación.
Su carrera como docente de programación se inició en el año 1997 en la Escuela Técnica ORT.
Actualmente coordina el desarrollo de proyectos web en Leeway y los cursos dictados en la Leeway Academy

One Comment

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *