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

About the author

Sophia Bennett is an art historian and freelance writer with a passion for exploring the intersections between nature, symbolism, and artistic expression. With a background in Renaissance and modern art, Sophia enjoys uncovering the hidden meanings behind iconic works and sharing her insights with art lovers of all levels. When she’s not visiting museums or researching the latest trends in contemporary art, you can find her hiking in the countryside, always chasing the next rainbow.