DEV Community

Cover image for 🧠Padrão de Projeto Strategy em TypeScript: Estratégias de Saque Bancário

🧠Padrão de Projeto Strategy em TypeScript: Estratégias de Saque Bancário

O Padrão Strategy (Estratégia) é um padrão de projeto de comportamento que permite definir uma família de algoritmos, encapsular cada um deles e torná-los intercambiáveis. Em outras palavras, você pode variar o algoritmo usado por um objeto em tempo de execução, escolhendo entre diferentes estratégias sem precisar alterar o código cliente. Essa flexibilidade evita estruturas rígidas no código e elimina a necessidade de múltiplos condicionais (ifs) para lidar com comportamentos diferentes. Em vez disso, cada comportamento variante é isolado em uma classe separada que implementa uma mesma interface de estratégia.

De forma simples, o Strategy ajuda a separar o que se faz (a lógica ou algoritmo) de como se faz (a implementação específica). O objeto principal (chamado de Contexto) delega a execução da tarefa para um objeto estratégico, através de uma interface comum. Isso permite trocar facilmente a estratégia em uso, promovendo código aberto para extensão e fechado para modificação (princípio OCP), podemos adicionar novas estratégias no futuro sem alterar o código existente.


🏦 Contexto: Estratégias de Saque Bancário

Para ilustrar o uso do padrão Strategy, vamos considerar um cenário de saque bancário. Imagine que um sistema bancário precisa suportar diferentes formas de retirar dinheiro de uma conta, como: saque em dinheiro (no caixa eletrônico), transferência TED (uma transferência bancária agendada) e transferência PIX (uma transferência eletrônica instantânea). Todas essas operações têm o mesmo objetivo – retirar um valor da conta do cliente – mas cada uma segue um procedimento diferente. Sem o Strategy, poderíamos ter um método sacar() cheio de condicionais para cada tipo de saque, tornando o código difícil de manter e estender.

Com o padrão Strategy, podemos criar uma interface EstrategiaSaque que declara o método geral de saque (por exemplo, sacar(valor)), e implementar uma classe concreta para cada modalidade: SaqueEmDinheiro, TransferenciaTed e TransferenciaPix. A classe de contexto, que pode ser a ContaBancaria ou um serviço de saque, vai manter uma referência a uma EstrategiaSaque e usá-la para realizar o saque solicitado. Dessa forma, a lógica de cada modalidade de saque fica isolada em sua própria classe, e a conta bancária não precisa saber os detalhes de cada procedimento, ela apenas delega à estratégia atual.


🛠️ Definindo a Interface de Estratégia e as Estratégias Concretas

Vamos começar definindo a interface de estratégia de saque e implementando as estratégias concretas em TypeScript. Cada estratégia terá seu próprio algoritmo para realizar o saque, mas todas compartilham o mesmo contrato (a interface). Usaremos comentários no código para explicar cada parte:

// Interface que define o contrato para estratégias de saque
interface EstrategiaSaque {
  sacar(valor: number): void;
}

// Estratégia concreta: Saque em Dinheiro (no caixa eletrônico)
class SaqueEmDinheiro implements EstrategiaSaque {
  sacar(valor: number): void {
    console.log(`Sacando R$${valor.toFixed(2)} em dinheiro...`);
    // Aqui poderíamos ter lógica de interação com ATM, dispensador de cédulas, etc.
    console.log("Dinheiro retirado da conta e entregue ao cliente.");
  }
}

// Estratégia concreta: Transferência TED (transferência eletrônica agendada)
class TransferenciaTed implements EstrategiaSaque {
  sacar(valor: number): void {
    console.log(`Iniciando transferência TED de R$${valor.toFixed(2)}...`);
    // Lógica simulada: agendar transferência para o próximo dia útil, etc.
    console.log("TED agendada com sucesso. Valor será debitado da conta e transferido.");
  }
}

// Estratégia concreta: Transferência PIX (transferência eletrônica instantânea)
class TransferenciaPix implements EstrategiaSaque {
  sacar(valor: number): void {
    console.log(`Realizando transferência PIX de R$${valor.toFixed(2)}...`);
    // Lógica simulada: transferência instantânea via PIX
    console.log("PIX concluído com sucesso. Valor debitado da conta imediatamente.");
  }
}
Enter fullscreen mode Exit fullscreen mode

Acima, definimos a interface EstrategiaSaque com o método sacar(valor) que todas as estratégias devem implementar. Em seguida, criamos três classes concretas, cada uma representando uma forma de saque bancário:

  • SaqueEmDinheiro: Simula a retirada de dinheiro físico em um caixa eletrônico, imprimindo mensagens correspondentes.
  • TransferenciaTed: Simula uma TED, que normalmente é concluída em horário comercial no mesmo dia ou dia seguinte.
  • TransferenciaPix: Simula um PIX, que é uma transferência instantânea disponível 24/7.

Cada classe implementa o método sacar(valor) de forma diferente, conforme a modalidade de saque. Note que usamos console.log() para representar as ações, mas em um sistema real aqui teríamos chamadas para serviços bancários, atualizações de saldo, etc. O importante é que a assinatura do método é igual para todas as estratégias, permitindo ao contexto usá-las de forma intercambiável.


📦 Classe Contexto: Conta Bancária usando uma Estratégia

Agora, vamos implementar a classe de contexto. Essa classe vai usar uma estratégia para executar a operação de saque sem conhecer os detalhes de qual estratégia está em uso. Escolhemos usar uma classe ContaBancaria como contexto, representando a conta do cliente de onde o dinheiro será retirado. A conta terá um método para definir a estratégia desejada e um método realizarSaque que delega a operação à estratégia definida:

class ContaBancaria {
  private estrategia: EstrategiaSaque;  // estratégia atual de saque
  private saldo: number;

  constructor(saldoInicial: number, estrategia: EstrategiaSaque) {
    this.saldo = saldoInicial;
    this.estrategia = estrategia;
  }

  // Permite alterar a estratégia de saque em tempo de execução
  definirEstrategia(estrategia: EstrategiaSaque): void {
    this.estrategia = estrategia;
  }

  // Realiza o saque utilizando a estratégia atual
  realizarSaque(valor: number): void {
    if (valor > this.saldo) {
      console.log("Saldo insuficiente para realizar o saque.");
      return;
    }
    console.log(`\nSaldo atual: R$${this.saldo.toFixed(2)}`);
    this.estrategia.sacar(valor);  // delega a execução do saque à estratégia selecionada
    this.saldo -= valor;
    console.log(`Saldo pós-saque: R$${this.saldo.toFixed(2)}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

No código acima, a classe ContaBancaria possui um atributo saldo para controle simples de saldo e um atributo estrategia do tipo EstrategiaSaque. No construtor, inicializamos a conta com um saldo inicial e uma estratégia de saque padrão (passada como parâmetro). O método definirEstrategia permite trocar a estratégia em tempo de execução, tornando o comportamento do saque flexível. Já o método realizarSaque verifica primeiro se há saldo suficiente e então registra o saldo atual, delega a operação de saque para o objeto estrategia atual (this.estrategia.sacar(valor)) e por fim atualiza e mostra o saldo restante.

Observe que ContaBancaria não sabe qual estratégia exata está sendo usada – ela apenas confia que o objeto em this.estrategia sabe executar o método sacar(valor). Assim, a conta bancária não precisa conter lógica específica para dinheiro, TED ou PIX; toda essa variação está encapsulada nas classes de estratégia. Isso ilustra o princípio do baixo acoplamento: o contexto (ContaBancaria) e as estratégias são fracamente ligados e podem variar independentemente.


🧪 Exemplo de Uso do Strategy Pattern

Vamos ver agora um exemplo de uso dessa implementação. Suponha que o cliente inicia com uma certa quantia em conta e deseja realizar diferentes tipos de saque. Podemos instanciar a ContaBancaria com uma estratégia inicial e depois alternar entre as estratégias para diferentes saques:

// Exemplo de uso do padrão Strategy
const conta = new ContaBancaria(1000, new SaqueEmDinheiro());  // saldo inicial de R$1000 e estratégia inicial: Saque em Dinheiro

conta.realizarSaque(200);  // Saque R$200 em dinheiro
// Saída esperada (resumida):
// Saldo atual: R$1000.00
// Sacando R$200.00 em dinheiro...
// Dinheiro retirado da conta e entregue ao cliente.
// Saldo pós-saque: R$800.00

// Alterando estratégia para transferência TED
conta.definirEstrategia(new TransferenciaTed());
conta.realizarSaque(150);  // Saque R$150 via TED
// Saída esperada (resumida):
// Saldo atual: R$800.00
// Iniciando transferência TED de R$150.00...
// TED agendada com sucesso. Valor será debitado da conta e transferido.
// Saldo pós-saque: R$650.00

// Alterando estratégia para transferência PIX
conta.definirEstrategia(new TransferenciaPix());
conta.realizarSaque(50);   // Saque R$50 via PIX
// Saída esperada (resumida):
// Saldo atual: R$650.00
// Realizando transferência PIX de R$50.00...
// PIX concluído com sucesso. Valor debitado da conta imediatamente.
// Saldo pós-saque: R$600.00
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, instanciamos a conta bancária com saldo de R\$1000 e estratégia inicial de saque em dinheiro. Em seguida:

  • Primeiro saque: Chamamos conta.realizarSaque(200) usando a estratégia SaqueEmDinheiro. O output (comentado no código) mostra que R$200 foram sacados em dinheiro e o saldo foi atualizado para R$800.
  • Segundo saque: Usamos conta.definirEstrategia(new TransferenciaTed()) para trocar a estratégia para TED, então conta.realizarSaque(150) realiza uma transferência TED de R$150. O saldo cai para R$650.
  • Terceiro saque: Alteramos a estratégia para PIX com conta.definirEstrategia(new TransferenciaPix()) e realizamos conta.realizarSaque(50), efetuando um PIX de R$50. O saldo final fica em R$600.

Perceba como conseguimos alternar entre diferentes comportamentos de saque em tempo de execução de forma simples. O código cliente (no caso, nosso script acima) pôde escolher a estratégia apropriada para cada situação e a classe ContaBancaria permaneceu inalterada durante essas trocas. Isso demonstra na prática o principal benefício do Strategy: adicionar ou mudar algoritmos sem modificar o contexto ou o código cliente que o utiliza. Se amanhã o banco introduzir uma nova modalidade de saque (por exemplo, boleto para retirar em lotérica), basta criar uma nova classe implementando EstrategiaSaque e configurar a conta para usá-la quando necessário, sem tocar nas outras classes existentes.


🧾 Diagrama de Sequência do Strategy Pattern

Para visualizar melhor a interação entre os objetos nesse padrão, confira o diagrama de sequência abaixo, que representa o fluxo de chamadas quando alternamos estratégias de saque em uma ContaBancaria:

Diagrama de Sequência do Strategy Pattern

No diagrama, o Cliente (pode ser o código principal ou parte da aplicação) primeiro define a estratégia de saque da ContaBancaria para SaqueEmDinheiro. Em seguida, ao solicitar realizarSaque(valor), a ContaBancaria delega a operação para o objeto SaqueEmDinheiro via chamada sacar(valor). O saque é executado e retornado o controle para a conta, que confirma ao cliente que a operação foi concluída. Depois, o cliente altera a estratégia para TransferenciaPIX e realiza outro saque. A ContaBancaria agora delega para TransferenciaPIX.sacar(outroValor), e assim por diante. Esse fluxo evidencia como o contexto permanece o mesmo, mudando apenas o objeto estratégia associado a ele conforme as necessidades do cliente.


✅ Benefícios e Considerações Finais

O padrão Strategy traz diversos benefícios para o design de software:

  • Flexibilidade e Extensibilidade: Novos comportamentos podem ser adicionados facilmente. Basta criar novas classes de estratégia sem modificar o código existente. Isso torna o sistema preparado para mudanças e novas exigências (seguindo o princípio aberto/fechado).
  • Evita Condicionais Complexas: Em vez de usar múltiplos if/else ou switch para tratar variações de algoritmo, delegamos para objetos polimórficos. Isso simplifica o código, melhora a legibilidade e facilita a manutenção.
  • Baixo Acoplamento: O contexto não precisa conhecer detalhes das estratégias concretas. Ele depende apenas da interface comum. Assim, cada estratégia pode ser desenvolvida, testada e alterada independentemente, sem impactar o resto do sistema.
  • Reutilização de Código: Estratégias diferentes podem compartilhar a mesma interface e até parte da lógica se for apropriado (por herança ou composição), evitando duplicação. Além disso, uma estratégia pode ser utilizada em diferentes contextos se fizer sentido, reaproveitando código.

Por outro lado, é importante notar que o uso do Strategy também traz complexidade extra em comparação a uma solução direta com condicionais simples. Se o número de algoritmos não é grande ou não tende a crescer, ou se não há necessidade real de trocar os comportamentos em tempo de execução, aplicar o padrão pode ser um excesso de abstração desnecessário. Portanto, avalie se a flexibilidade proporcionada pelo Strategy é necessária para o seu caso. Quando bem empregado, entretanto, este padrão resulta em um código mais limpo, organizado e aderente a boas práticas de design.


🏁 Conclusão

Neste artigo, exploramos o Padrão de Projeto Strategy utilizando TypeScript e um exemplo prático de estratégias de saque bancário (dinheiro, TED, PIX). Vimos como esse padrão nos permite alternar algoritmos dinamicamente em tempo de execução de forma transparente para o código cliente, mantendo cada implementação de algoritmo isolada e facilitando a expansão do sistema sem modificações drásticas no código existente. Apresentamos a interface de estratégia, três estratégias concretas e uma classe de contexto (ContaBancaria) que utiliza essas estratégias. Também analisamos um diagrama de sequência para solidificar a compreensão da interação entre contexto e estratégias.

O Strategy Pattern é uma poderosa ferramenta para melhorar a organização e flexibilidade do código. Ele é especialmente útil em cenários onde há muitas variações de comportamentos – como diferentes métodos de pagamento ou formas de cálculo – que podem mudar ou se expandir com o tempo. Ao aplicar este padrão, você estará escrevendo código mais manutenível, extensível e alinhado com princípios SOLID de design de software.

Esperamos que este guia tenha tornado o conceito de Strategy claro. Compreender e aplicar padrões de projeto é uma habilidade valiosa que melhora a qualidade do software e a produtividade do desenvolvedor. Experimente incorporar o Strategy em seus projetos quando enfrentar problemas similares, e aproveite os benefícios de um código bem estruturado e flexível.


📚 Referências


💡Curtiu?

Se quiser trocar ideia sobre IA, cloud e arquitetura, me segue nas redes:

Publico conteúdos técnicos direto do campo de batalha. E quando descubro uma ferramenta que economiza tempo e resolve bem, como essa, você fica sabendo também.

Top comments (0)