El patrón Strategy es un patrón de diseño de comportamiento que proporciona un mecanismo para seleccionar un algoritmo en tiempo de ejecución de una familia de algoritmos, y hacerlos intercambiables. 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

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 aplicación de navegación puede utilizar diferentes estrategias para coches, peatones o ciclistas.
Algoritmos de ordenación: Los distintos algoritmos de ordenación (quicksort, bubble sort, merge sort) pueden implementarse como estrategias, lo que permite elegir la más adecuada en tiempo de ejecución.
na ventaja importante del patrón Estrategia es que permite a los usuarios elegir diferentes estrategias en tiempo de ejecución.✨

Glosario 🌍

El glosario tendrá más sentido cuando visitemos nuestro ejemplo.

1. Contexto:

El Contexto tiene una referencia a una de las estrategias concretas y que es utilizada por sus otros métodos. Para ello, 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. 3. Estrategias concretas:

Las estrategias concretas son implementaciones de algoritmos que utiliza el cliente y el contexto. Implementan la interfaz de estrategia.
. El Cliente: inicializa un objeto de estrategia específico y lo pasa al contexto a través de setStategy(stategy).

ejemplos
Ejemplo 1: Aplicación de envío

Suponga que tiene una aplicación de comercio electrónico. Quieres 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  a interfaz de la estrategia de envío 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.

ur nuevo ShippingV2Component:
Código 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: evisando la app Notes vía Factory Pattern

¿Os dais cuenta del problema? en cada llamada llamamos a createNoteService que crea y devuelve nueva referencia necesariamente. Esto puede causar pérdidas de memoria. Mejorandolo podemos crear el servicio bajo demanda cuando hay un cambio en la red. T Esta solución se asemeja al patrón de estrategia.

En este artículo se proporciona la solución actualizada para este problema a través de Strategy Pattern.

Esperamos que os haya gustado y os haya sido útil. nks para reading.

🍍Fuente