DEV Community

Cover image for Utiliza el patrón de diseño Strategy 👾
Jean Carlo Vittory Laguna
Jean Carlo Vittory Laguna

Posted on

Utiliza el patrón de diseño Strategy 👾

El patrón de diseño Strategy es una técnica utilizada para definir una serie de algoritmos, encapsularlos y así poder hacerlos intercambiables de acuerdo a las necesidades del usuario.

Este diseño hace parte de la familia de patrones comportamentales.

Podemos ejemplificar lo anterior imaginando que necesitamos definir, de acuerdo al método de pago de nuestro cliente, una serie de cálculos y acciones para poder ejecutar la transacción final.

A veces lo primero que se nos viene a la mente es utilizar un if...else y a partir de un parámetro de entrada definir distintos caminos en nuestro programa. Si seguimos esta idea pronto nos encontraremos con problemas de legibilidad, escalado y la refactorización puede llegar a ser compleja dependiendo de la complejidad de nuestro programa.

Por lo tanto el patrón Strategy nos permite encapsular acciones, según el método de pago elegido por nuestro cliente en nuestro ejemplo, y llamar una estrategia u otra. Si necesitamos ingresar otro método de pago que conlleve una serie de cálculos y procesos nuevos sólo basta con crear otra estrategia, con su respectivo algoritmo encapsulado y llamarlo.

Implementación

Para lograr lo anterior debemos partir de la idea de que el patrón Strategy usa como punto de partida una interfaz compartida por todas las diferentes estrategias que queramos crear.

Continuando con el ejemplo anterior, nuestra acción inicial es el proceso de pagos por lo tanto sabemos que esa acción será compartida por nuestras estrategias. Debemos generar una interfaz que describa dicha acción:

export interface IPaymentMethod {
  processPayment(paymentQuantity: number): Promise<string>;
}
Enter fullscreen mode Exit fullscreen mode

Esta función nos devolverá un mensaje de ejemplo pero puedes modificarlo según sea tu caso

Luego de esto debemos crear nuestras estrategias las cuales implementarán en su interior un método, tal como lo describe la interfaz que creamos anteriormente, que se encargará de procesar, calcular y ejecutar lo que necesitemos según sea el caso.

import {
  IPaymentMethod,
  debitCardTypes,
  delaysDebitCards,
  creditCardTypes,
  delaysCreditCards,
} from "./interfaces";

export class CashStrategy implements IPaymentMethod {
  processPayment(paymentQuantity: number): Promise<string> {
    return new Promise((resolve) => {
      try {
        setTimeout(() => {
          resolve(
            `The payment was succesfully. You paid ${paymentQuantity} in cash`
          );
        }, 1000);
      } catch (error: any) {
        resolve("error");
      }
    });
  }
}

export class DebitCardStrategy implements IPaymentMethod {
  private debitCard: string;

  constructor(debitCardTypeInput: string) {
    this.debitCard = debitCardTypeInput;
  }

  processPayment(paymentQuantity: number): Promise<string> {
    const selectedCard = this.debitCard;
    if (
      Object.values(debitCardTypes).includes(selectedCard as debitCardTypes)
    ) {
      const delay = delaysDebitCards[selectedCard as debitCardTypes];
      return new Promise((resolve, reject) => {
        try {
          setTimeout(() => {
            resolve(
              `The payment was succesfully. You paid ${paymentQuantity} with ${selectedCard}`
            );
          }, delay);
        } catch (error: any) {
          reject("error");
        }
      });
    } else {
      throw "The selected card do not exist.";
    }
  }
}

export class CreditCardStrategy implements IPaymentMethod {
  private creditCard: string;

  constructor(creditCardTypeInput: string) {
    this.creditCard = creditCardTypeInput;
  }

  processPayment(paymentQuantity: number): Promise<string> {
    const selectedCard = this.creditCard;
    if (
      Object.values(creditCardTypes).includes(selectedCard as creditCardTypes)
    ) {
      const delay = delaysCreditCards[selectedCard as creditCardTypes];
      return new Promise((resolve, reject) => {
        try {
          setTimeout(() => {
            resolve(
              `The payment was succesfully. You paid ${paymentQuantity} with ${selectedCard}`
            );
          }, delay);
        } catch (error: any) {
          reject("error");
        }
      });
    } else {
      throw "The selected card do not exist.";
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Hemos utilizado un setTimeout para simular un retraso en cada estrategia y simular un comportamiento asíncrono

Luego de crear las estrategias debemos crear la clase que instanciara nuestros pagos y desde allí ejecutaremos o seleccionaremos la estrategia que necesitemos:

import { CashStrategy } from "./strategys";
import { IPaymentMethod, IPayments } from "./interfaces";

export class Payments implements IPayments {
  public paymentMethod: IPaymentMethod = new CashStrategy();

  setPaymentMethod(paymentStrategy: IPaymentMethod): void {
    this.paymentMethod = paymentStrategy;
  }

  applyTransaction(paymentQuantity: number): Promise<string> {
    return this.paymentMethod.processPayment(paymentQuantity);
  }
}
Enter fullscreen mode Exit fullscreen mode

Por defecto nuestra clase Payment instancia CashStrategy que se encarga de procesar transacciones en efectivo.

Por ultimo solo nos queda ejecutar nuestro programa e instanciar una u otra estrategia según sea el caso:

import { Payments } from "./payments";
import { DebitCardStrategy, CreditCardStrategy } from "./strategys";

const payment = new Payments();

const init = async () => {
  payment.setPaymentMethod(new CreditCardStrategy("American Express"));
  return await payment.applyTransaction(200);
};
init().then((result) => console.log(result));
Enter fullscreen mode Exit fullscreen mode

Desventajas

Una de las principales desventajas de este patrón de diseño es que agrega un nivel de complejidad adicional al código en especial cuando tenemos múltiples estrategias y debemos coordinarlas entre ellas.

Al tratarse de clases, la configuración inicial y la conservación del estado entre las instancias puede llegar a ser un proceso complejo así como la compilación de nuestro programa cuando nuestras clases empiezan a aumentar.

A pesar de todo esto el patrón de estrategia nos permite solventar múltiples situaciones y si sabemos aplicarlos nos puede brindar soluciones a diferentes problemas que afrontamos como programadores.

Te adjunto el repositorio donde encontraras el código de este ejemplo.

Repositorio

Top comments (0)