ComportamentalIniciante6 min de leitura10 seções1994, Marco de referência: a ideia é mais velha que a data

Strategy

Encapsula uma família de algoritmos intercambiáveis por trás de uma interface comum, deixando o cliente trocar a estratégia em tempo de execução.

Em 10 segundos

Cada variação de um algoritmo vira uma classe atrás de uma interface comum; o contexto só delega. Trocar de comportamento é injetar outra estratégia — sem if/else e sem tocar no código que já funciona.

  • #gof
  • #polimorfismo
  • #algoritmos
  • #composicao
Analogia

Como o app de mapas escolhe a rota

Mesmo destino, mas você troca entre 'mais rápida', 'sem pedágio' ou 'a pé'. Cada opção é um algoritmo diferente calculando o mesmo resultado (a rota). Você troca a estratégia sem trocar o mapa. Strategy é isso: algoritmos intercambiáveis atrás da mesma interface.

01Conceito

O problema

Uma tarefa tem várias variações de algoritmo (frete por transportadora, desconto por cliente...). Concentrar tudo numa classe vira um emaranhado de if/else que cresce a cada variação.

E isso viola o Aberto/Fechado: cada novo comportamento obriga a mexer no código que já funciona.

A raiz do problema é misturar o que é estável com o que varia. A estrutura da tarefa ('calcular o total do carrinho') quase não muda; a regra interna ('como calcular o frete') muda toda hora. Enquanto os dois moram na mesma classe, cada mudança na parte volátil arrisca a parte estável — e o condicional que escolhe a variação se duplica em todo lugar que precisa da mesma decisão.

A alternativa clássica é herança: criar CarrinhoComSedex, CarrinhoComPac... Funciona até precisar variar uma segunda dimensão (desconto, embalagem) — aí as subclasses explodem em combinações, e o comportamento fica fixado em tempo de compilação. Strategy troca herança por composição: o comportamento é um objeto que se injeta e se troca em runtime, e cada dimensão de variação vira uma família de estratégias independente.

Uma nuance de linguagem: em TypeScript, Python e afins, uma estratégia pode ser só uma função — passar calcularFrete como parâmetro já é Strategy em espírito. A classe vale a pena quando a estratégia tem dependências próprias, várias operações relacionadas ou precisa de configuração; o padrão é o contrato estável, não a cerimônia de classes.

Como se encaixa
  1. Cliente
  2. injeta e chamaContexto
  3. delega executar()Interface Estrategia
  4. despacha paraEstratégia concreta
O contexto enxerga só a interface — qual algoritmo roda é decisão de quem injeta, em tempo de execução.
Sem o padrão × com o padrão

Com Strategy: uma classe por regra

  • FreteSedex, FretePac, FreteRetirada implementam a mesma interface
  • modalidade nova é arquivo novo — o cálculo existente não é tocado
  • cada regra é testável isolada, sem montar o contexto
  • quem injeta decide, em tempo de execução

O custo se desloca para a indireção: mais arquivos e um salto a mais para ler o fluxo. Vale quando as regras mudam com frequência — não quando são duas e estáveis.

O padrão não elimina a complexidade das regras: ele troca ramificação dentro de um método por polimorfismo entre classes. A pergunta certa é se as regras mudam o bastante para pagar a indireção.
02Interativo

Troque a estratégia e veja recalcular

Mexa nos controles: a teoria acima vira comportamento observável.

3

Frete (Normal · 5 dias)

R$ 11.00

O cálculo (algoritmo) é trocável em runtime. O cliente só conhece a interfacecalcularFrete(peso).

Passo a passoO fluxo, passo a passo
  1. 1

    Definir a interface

    Uma interface comum descreve o algoritmo (ex.: calcularFrete).

  2. 2

    Implementar variações

    Cada algoritmo vira uma classe que implementa a interface.

  3. 3

    Injetar no contexto

    O contexto recebe uma estratégia e guarda a referência.

  4. 4

    Delegar

    O contexto chama a estratégia — e você pode trocá-la em runtime.

03Anatomia

Navegue pelas camadas

Camada 1/4Cliente

Camada 1

Cliente

escolhe e injeta a estratégia

É o único que conhece as estratégias concretas. Decide qual usar — por escolha do usuário, configuração, feature flag — e injeta no contexto. Concentrar a escolha aqui mantém o resto do sistema ignorante das variações.

No código

const carrinho = new Carrinho(new Pac());
carrinho.usarFrete(new Sedex()); // troca em runtime

Se violar: se a escolha se espalhar por vários pontos do código, cada estratégia nova exige caçar todos os lugares que decidem — o condicional voltou, só que distribuído.

04Estrutura

Estrutura (diagrama de classes)

Diagrama de classes
05Implementação

Código

interface Frete {
  calcular(pesoKg: number): number;
}

class Sedex implements Frete {
  calcular(pesoKg: number) { return 20 + pesoKg * 3; }
}
class Pac implements Frete {
  calcular(pesoKg: number) { return 12 + pesoKg * 1.5; }
}

class Carrinho {
  constructor(private frete: Frete) {}
  usarFrete(f: Frete) { this.frete = f; }
  total(subtotal: number, pesoKg: number) {
    return subtotal + this.frete.calcular(pesoKg);
  }
}

const carrinho = new Carrinho(new Pac());
console.log(carrinho.total(100, 2));   // 115
carrinho.usarFrete(new Sedex());
console.log(carrinho.total(100, 2));   // 126
06No mundo real

Casos de uso

Cálculo de frete por transportadora

Cenário

Um e-commerce cotiza com Correios, transportadora própria e retirada na loja — cada uma com tabela, prazo e regra de peso diferentes, e novas parcerias chegando todo trimestre.

Como o padrão entra

Interface Frete com calcular(); uma classe por transportadora; o checkout injeta a estratégia conforme a escolha do usuário. Parceria nova = classe nova, sem tocar no fluxo do pedido.

Trade-off: Alguém ainda precisa mapear escolha→estratégia; na prática esse papel vai para uma factory, e o par Strategy + Factory vira o desenho completo.

Precificação por tipo de cliente

Cenário

Varejo, atacado e assinante premium pagam preços diferentes pelo mesmo catálogo, e o comercial inventa política nova a cada campanha.

Como o padrão entra

Uma PoliticaPreco por perfil, selecionada no login ou na sessão. Cada política é testada isoladamente com casos de borda próprios — e a campanha nova entra sem regressão nas demais.

Trade-off: Quando as dimensões se multiplicam (perfil × campanha × região), uma estratégia por combinação explode; passa a ser preciso compor estratégias, e composição tem custo próprio de design.

Compressão e serialização plugáveis

Cenário

Um serviço exporta relatórios em JSON, CSV ou Parquet, e comprime a resposta com gzip ou brotli conforme o que o cliente aceita.

Como o padrão entra

Interfaces Serializador e Compressor; a escolha vem do content-type e do header Accept-Encoding. O pipeline de exportação é escrito uma vez e nunca sabe qual formato está gerando.

Trade-off: Formatos têm capacidades desiguais (streaming, tipos ricos, schema) — a interface comum nivela por baixo, e recursos exclusivos de um formato ficam de fora do contrato.

A Strategy que continua sendo um if/else

Compila, roda, passa no code review. E quebra depois.

As classes de estratégia existem, implementam a interface, tudo parece certo — mas quem escolhe qual usar é um switch dentro da própria classe que deveria ignorar a escolha. O polimorfismo foi adiado, não aplicado.

interface Frete { calcular(peso: number): number; }
class Sedex implements Frete { calcular(p: number) { return p * 2.5; } }
class Pac   implements Frete { calcular(p: number) { return p * 1.2; } }

class Carrinho {
  // A interface existe... e a decisao continua aqui dentro.
  private estrategia(tipo: string): Frete {
    switch (tipo) {
      case "sedex": return new Sedex();
      case "pac":   return new Pac();
      default: throw new Error("tipo desconhecido");
    }
  }

  total(peso: number, tipo: string) {
    return this.estrategia(tipo).calcular(peso);  // <- Carrinho conhece TODAS
  }
}

// Transportadora nova = editar Carrinho. O Aberto/Fechado continua violado,
// so que agora com tres arquivos em vez de um.

O que quebra

  • Ao adicionar varianteÉ preciso editar a classe que deveria ser fechada para modificação — exatamente o que o padrão prometia evitar.
  • No testeTestar Carrinho exige conhecer todas as estratégias reais, porque não há como injetar uma falsa.
  • Na revisãoO switch se multiplica: aparece de novo em cada lugar que precisa escolher, e eles saem de sincronia.

A saída: A estratégia entra de fora — por construtor ou parâmetro. Se ainda for preciso mapear string para implementação, esse mapa vive na composição da aplicação (ou numa Factory), e não dentro de quem usa a estratégia.

Do cheiro ao padrão

Um passo por vez, com o motivo de cada um.

O cheiro: Um switch que cresce a cada regra nova de frete, dentro da classe que só queria somar o carrinho.

Como está. Um switch que cresce a cada regra nova de frete, dentro da classe que só queria somar o carrinho.

class Carrinho {
  total(peso: number, tipo: string) {
    let frete = 0;
    if (tipo === "sedex") frete = peso * 2.5;
    else if (tipo === "pac") frete = peso * 1.2;
    else if (tipo === "retirada") frete = 0;
    else throw new Error("tipo desconhecido");
    return this.soma() + frete;
  }
}
09Cuidado

Armadilhas

  1. Strategy para duas variações estáveis

    Interface + duas classes + injeção para um comportamento que tem duas ramificações e não muda há dois anos é overkill: um if legível ganha em clareza e custo. O padrão paga o próprio preço quando a variação é real e crescente — aplique quando a terceira variação aparecer no horizonte, não antes (YAGNI).

  2. O if/else gigante só mudou de lugar

    Se o cliente escolhe a estratégia com uma cadeia enorme de condicionais, o emaranhado migrou do contexto para o cliente — o problema trocou de endereço, não sumiu. Combine com Factory: um mapa de nome→estratégia reduz a escolha a um lookup e concentra o cadastro de variações num único lugar.

  3. Estratégias com estado compartilhado

    Estratégia que acumula estado entre chamadas ou mexe em estado do contexto deixa de ser intercambiável: trocar no meio de uma operação corrompe dados, e uma instância compartilhada entre requests vira condição de corrida. Prefira estratégias stateless que recebem tudo por parâmetro — o que também as torna triviais de testar.

10Decisão

Quando usar × quando evitar

Use quando

3
  • Há várias maneiras de fazer a mesma coisa e você quer escolher uma em tempo de execução.
  • Uma classe está cheia de condicionais que selecionam comportamentos parecidos.
  • Você quer isolar um algoritmo volátil do restante do código.

Evite quando

2
  • Só existem poucas variações estáveis que raramente mudam — a indireção não compensa.
  • O cliente teria de conhecer as diferenças entre estratégias para escolher.

Testar o que ficou

3 armadilhas de Strategy viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • O comparador de Array.sortO algoritmo de ordenação é fixo; o critério de comparação entra por parâmetro.
  • Strategies do PassportO nome é literal: local, OAuth e JWT são estratégias plugáveis atrás de uma interface só.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// A estrategia entra de fora. So isso.
const total = (itens, calcularFrete) =>
  soma(itens) + calcularFrete(peso(itens));

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma interface e uma classe (ou função) por variante
  • A escolha de qual estratégia usar migra para quem compõe — e precisa morar em algum lugar

Não vale se: existem duas variantes e elas nunca mudam. Um if é mais honesto que uma hierarquia.

Costuma ser confundido com

Onde isto aparece

Continue por aqui