Como implementar o padrão Strategy utilizando Java e Spring

O que é o Padrão Strategy?

O padrão Strategy é um padrão de design comportamental que permite definir uma família de algoritmos, encapsulá-los e torná-los intercambiáveis. Esse padrão permite que o algoritmo varie independentemente dos clientes que o utilizam. Em outras palavras, ele permite que você escolha o algoritmo a ser usado em tempo de execução.

Benefícios do Padrão Strategy

  1. Manutenção Facilitada: Com o Strategy, você pode adicionar novas estratégias (algoritmos) sem modificar o código existente, seguindo o princípio aberto/fechado (Open/Closed Principle).
  2. Redução da Duplicação de Código: Encapsulando algoritmos em classes separadas, evitamos duplicação de código e promovemos o reuso.
  3. Melhoria na Testabilidade: Testar cada estratégia isoladamente é mais fácil do que testar uma classe com múltiplos comportamentos embutidos.
  4. Flexibilidade: Permite que os algoritmos sejam escolhidos e alterados em tempo de execução, oferecendo maior flexibilidade na aplicação.

Quando usar o Padrão Strategy?

  • Quando você tem muitas classes relacionadas que diferem apenas no comportamento.
  • Quando você precisa de diferentes variações de um algoritmo.
  • Quando os algoritmos específicos das classes podem ser expostos para que outros objetos possam interagir com eles.
  • Substituição de if e switch-case.

Exemplo de Implementação em Java

Enum para auxiliar na escolha de estratégia:
package com.example.demo.strategy;

public enum DiscountType {
    CHRISTMAS,
    NEW_YEAR,
    EASTER
}

Interface das estratégias:
package com.example.demo.strategy;

import java.math.BigDecimal;

public interface DiscountStrategy {

    BigDecimal applyDiscount(BigDecimal price);

    boolean selector(DiscountType discountType);
}
Estratégias
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class ChristmasDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.90)); // 10% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.CHRISTMAS.equals(discountType);
    }
}
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class EasterDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.80)); // 20% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.EASTER.equals(discountType);
    }
}
package com.example.demo.strategy;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class NewYearDiscountStrategy implements DiscountStrategy{
    @Override
    public BigDecimal applyDiscount(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(0.85)); // 15% de desconto
    }

    @Override
    public boolean selector(DiscountType discountType) {
        return DiscountType.NEW_YEAR.equals(discountType);
    }
}
Como implementar a seleção da estratégia

É muito comum ver essa seleção de estratégia sendo implementada com a inserção manual dessas mesmas estratégias na lista. Mas podemos utilizar o próprio spring pra isso, tendo declarado todas estratégias como componentes do spring. Como fizemos acima. Então quando criamos uma lista de estratégias, e pedimos o lombok pra criar um construtor com argumentos necessários, as estratégias já são inseridas automaticamente, sem a necessidade de inserção manual.

package com.example.demo.service;

import com.example.demo.strategy.DiscountStrategy;
import com.example.demo.strategy.DiscountType;
import jakarta.el.MethodNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class DiscountService {

    private final List<DiscountStrategy> strategies;
    public DiscountStrategy getStrategy(DiscountType discountType){
        return strategies.stream().filter(strategy -> strategy.selector(discountType))
                .findFirst()
                .orElseThrow(MethodNotFoundException::new);
    }
}

Conclusão

O padrão Strategy é uma poderosa ferramenta para separar os algoritmos das classes que os utilizam, permitindo flexibilidade e facilidade de manutenção. É especialmente útil em situações onde você precisa de múltiplas variações de um algoritmo e deseja selecionar qual usar em tempo de execução.

Código completo: https://github.com/nathalia-amarals/strategy-java-spring-boot

Referências:

Deixe um comentário