En el contexto de Angular, aquí puedes pensar en algoritmos como servicios, pero también se puede utilizar para componentes y clases.
🗎 Código fuente
➡️ Prerrequisito recomendado
💎Versión mejorada demarkdown

Principales ventajas

  • Propósito: Definir una familia de algoritmos intercambiables y permitir al cliente elegir dinámicamente cuál utilizar en tiempo de ejecución. Esto proporciona flexibilidad.
  • Encapsulación: Cada estrategia se encapsula como una clase independiente, lo que ayuda a mantener el código limpio y organizado. El código del cliente no conoce los detalles de cómo se implementa cada estrategia, ya que sólo interactúa con la interfaz común.
  • Composición sobre herencia: El patrón de estrategia se basa en la composición para lograr la reutilización del comportamiento en lugar de depender de la herencia. Esto conduce a diseños más flexibles.
CPU
1 vCPU
MEMORIA
1 GB
ALMACENAMIENTO
10 GB
TRANSFERENCIA
1 TB
PRECIO
$ 4 mes
Para obtener el servidor GRATIS debes de escribir el cupon "LEIFER"

Escenarios útiles 💎

Cuando se tienen múltiples algoritmos o enfoques que pueden usarse indistintamente para resolver un problema.

Escenarios clásicos

  • Aplicación de navegación: Una navegación podría utilizar diferentes estrategias de enrutamiento para coches, peatones o ciclistas.
  • Algoritmos de ordenación: Diferentes algoritmos de ordenación (quicksort, bubble sort, merge sort) pueden implementarse como estrategias, permitiéndole elegir el más adecuado en tiempo de ejecución.

Una ventaja importante del patrón Estrategia es que permite a los usuarios elegir diferentes estrategias en tiempo de ejecución.✨

Glosario 🌍

Glosario va a tener más sentido cuando visitamos nuestro ejemplo.

1. Contexto:
El Contexto tiene una referencia a una de las estrategias concretas y que es utilizada por el resto de sus métodos. Para conseguirlo, la clase contexto suele tener un método público llamado setStategy(stategy) que es establecido por el cliente. Esa referencia es de tipo Strategy Interface. El contexto no sabe qué estrategia utiliza, lo fundamental es que la estrategia implemente Strategy Interface.

2. Interfaz de estrategia:
Es común a todas las estrategias concretas. Define un plano que es implementado por las estrategias concretas.

3. Estrategias concretas:
Las Estrategias Concretas son implementaciones de algoritmos que son usados por el cliente y usados por el contexto. Implementan la Interfaz de Estrategias.

4. El Cliente: inicializa un objeto estrategia concreto y se lo pasa al contexto mediante setStategy(stategy)

Ejemplos

Ejemplo 1: Aplicación de envío

Suponga que tiene una aplicación de comercio electrónico. Desea proporcionar información de envío basada en las preferencias del cliente.

En el contexto de este post, desea proporcionar estrategias intercambiables para elegir.

Problema
Examinemos primero el enfoque ingenuo/de fuerza bruta: ⬇️
Código fuente v1

export class ShippingV1Component {
  public readonly shippingOptions = ['EXPRESS', 'ECONOMY'];
  public selectedOption!: string;
  public type?: string;
  public cost?: string;
  public estimatedTime?: string;
​
  constructor(
    private readonly expressShippping: ExpressShippingService,
    private readonly economyShipping: EconomyShippingService
  ) {}
​
  public onStrategyChange(option: string): void {
    this.selectedOption = option;
    this.getData(option);
  }
​
  private getData(option: string): void {
    if (option === 'EXPRESS') {
      this.type = this.expressShippping.getType();
      this.cost = this.expressShippping.getCost();
      this.estimatedTime = this.expressShippping.getEstimatedTime();
    } else if (option === 'ECONOMY') {
      this.type = this.economyShipping.getType();
      this.cost = this.economyShipping.getCost();
      this.estimatedTime = this.economyShipping.getEstimatedTime();
    }
  }
}

Como ves en el método getData ya tenemos una condición no deseada. Esto puede complicarse más. ¿Qué pasa si introducimos nuevos servicios de envío como Sea Shipping y así sucesivamente? Vamos a tener más complejidad. 😕

Solución: 🛠

El siguiente diagrama muestra cómo nuestra implementación final debe ser similar:

Paso 1: Definir una interfaz de estrategia para estrategias concretas
La interfaz de la estrategia de transporte es común a todas las variantes de las estrategias.

export interface IShippingStrategy {
    getType: () => string;
    getCost: () => string;
    getEstimatedTime: () => string;
}

Paso 2: Crear estrategias concretas que implementen IShippingStrategy

@Injectable({
  providedIn: 'root',
})
export class EconomyShippingService implements IShippingStrategy {
  public getType(): string {
    return 'ECONOMY';
  }
​
  public getCost(): string {
    return '15$';
  }
​
  public getEstimatedTime(): string {
    return '5-12 days';
  }
}​


y

@Injectable({
  providedIn: 'root'
})
export class ExpressShippingService implements IShippingStrategy {
  public getType(): string {
    return 'EXPRESS';
  }
​
  public getCost(): string {
    return '100$';
  };
​
  public getEstimatedTime(): string {
    return '1-2 days';
  };
}

Paso 3: Crear un servicio de contexto que tenga referencia a estrategias concretas.

@Injectable({
  providedIn: 'root',
})
export class ShippingContextService implements IShippingStrategy {
  private strategy!: IShippingStrategy;
​
  public hasChosenStrategy(): boolean {
    return !!this.strategy;
  }
​
  public setStrategy(strategy: IShippingStrategy): void {
    this.strategy = strategy;
  }
​
  public getType(): string {
    return this.strategy.getType();
  }
​
  public getCost(): string {
    return this.strategy.getCost();
  }
​
  public getEstimatedTime(): string {
    return this.strategy.getEstimatedTime();
  }
}

El método setStrategy es llamado por el cliente.

El contexto no se limita a la interfaz IShippingStrategy. También puede tener métodos específicos compartidos por todas las estrategias concretas.

Paso 4: Permitir al cliente elegir la estrategia preferida.
Nuestro nuevo ShippingV2Component:
Código fuente v2

export class ShippingV2Component {
  public readonly shippingOptions = ['EXPRESS', 'ECONOMY'];
  public selectedOption!: string;
  public type?: string;
  public cost?: string;
  public estimatedTime?: string;
​
  constructor(
    private readonly injector: Injector,
    private readonly shippingContext: ShippingContextService
  ) {}
​
  public onStrategyChange(option: string): void {
    this.selectedOption = option;
​
    switch (option) {
      case 'EXPRESS': {
        const strategy = this.injector.get(ExpressShippingService);
        this.shippingContext.setStrategy(strategy);
        break;
      }
​
      case 'ECONOMY': {
        const strategy = this.injector.get(EconomyShippingService);
        this.shippingContext.setStrategy(strategy);
        break;
      }
    }
    this.getData();
  }
​
  private getData(): void {
    if (!this.shippingContext.hasChosenStrategy) {
      return;
    }
    this.type = this.shippingContext.getType();
    this.cost = this.shippingContext.getCost();
    this.estimatedTime = this.shippingContext.getEstimatedTime();
  }
}


Como has notado usamos la condición solo una vez en onStrategyChange cuando el Cliente elige la opción de envío.

Eso es todo 🙂 .

Ejemplo 2: Revisión de la app Notes a través del Factory Pattern

Por favor lee el post anterior de Factory Pattern para este ejemplo.

En el post de Factory Pattern puse esta advertencia:

¿Te das cuenta del problema aquí? En cada llamada llamamos a createNoteService que crea y devuelve nueva referencia necesariamente. Esto puede causar pérdidas de memoria. Para mejorarlo podemos crear el serviciobajo demanda cuando hay un cambio en la red. Esta solución de abajo se asemeja al patrón deestrategia.

A continuación, proporcionamos la solución actualizada para este problema a través de Strategy Pattern.

Eso es todo espero que lo hayan disfrutado y encontrado útil. Gracias por leer.🍍

Referencias

Fuente