Você já se deparou com um código em que os if-else não paravam de crescer para cada forma de pagamento?
Quando opções de ordenação, cálculo de descontos e lógica de filtros começam a se misturar em um único switch enorme, fica cada vez mais assustador mexer no código.
É nessa hora que entra o padrão Strategy do Swift.
Hoje vamos explicar, sob uma perspectiva prática, como trocar algoritmos como se fossem componentes usando protocolos e closures.
Vamos começar pelo essencial.
O padrão Strategy separa o que fazer de como fazer, permitindo trocar algoritmos externamente.
No Swift, isso pode ser implementado de duas formas: com protocolos, uma abordagem mais pesada, ou com closures, uma abordagem mais leve.
Vamos mostrar cada uma delas a seguir.
O que é o padrão Strategy? Resumo em 3 linhas
Vamos definir a estrutura antes de entrar em teorias complicadas.
- Agrupar os algoritmos que serão trocados sob uma especificação comum (protocolo)
- Criar o comportamento real como objetos de estratégia ou closures separados
- Quem usa conhece apenas a especificação e não precisa conhecer os detalhes concretos
É como trocar a arma de um personagem de jogo.
O personagem só precisa saber “atacar”; seja espada, arco ou magia, a arma equipada cuida do resto.
Você troca apenas a arma sem alterar o código do personagem.
Essa troca é praticamente todo o padrão Strategy.
Implementando com protocolo (a abordagem clássica)
Vamos começar pela abordagem com protocolo mais tradicional.
Usaremos o cálculo de descontos como exemplo. Há vários métodos de cálculo, como membro comum, membro VIP e aplicação de cupom.
O código abaixo define a especificação comum que toda estratégia deve seguir e uma estratégia concreta.
// Especificação comum que toda estratégia de desconto deve seguir
protocol DiscountStrategy {
func discount(for price: Int) -> Int
}
// VIP Estratégia: 20% desconto
struct VIPDiscount: DiscountStrategy {
func discount(for price: Int) -> Int { price * 20 / 100 }
}
Agora vamos ver quem armazena e usa essa estratégia.
O objeto cliente não se importa com qual estratégia recebe.
struct Checkout {
var strategy: DiscountStrategy // Local onde a estratégia pode ser trocada
func finalPrice(_ price: Int) -> Int {
price - strategy.discount(for: price)
}
}
// Trocar apenas a estratégia no momento do pagamento
let cart = Checkout(strategy: VIPDiscount())
Mesmo com uma nova política de descontos, você não precisa alterar Checkout.
Basta criar uma nova estrutura que siga DiscountStrategy.
Ampliar funcionalidades sem tocar no código existente: essa é a maior vantagem percebida no dia a dia.
Implementando com closures (a abordagem idiomática do Swift)
Mas criar uma estrutura toda vez que a estratégia é simples acaba sendo trabalhoso.
Envolver toda lógica simples em um protocolo pode deixar o código mais verboso.
Nesses casos, closures são muito mais leves e naturais no Swift.
struct Checkout {
// Receber a própria estratégia como uma função
var discount: (Int) -> Int
func finalPrice(_ price: Int) -> Int {
price - discount(price)
}
}
// Definir a estratégia diretamente no local
let cart = Checkout(discount: { $0 * 20 / 100 })
Sem declarar um tipo separado, você pode escrever a lógica no momento em que a passa.
Para trocar algoritmos leves, como critérios de ordenação, condições de filtro e transformações simples, essa abordagem é muito mais elegante.
Na verdade, sorted(by:) da biblioteca padrão é exatamente esse padrão Strategy baseado em closure.
A estrutura da ordenação permanece fixa, e apenas o critério de comparação é trocado por um closure.
Ou seja, já o usamos todos os dias.
Protocolos e closures: quando usar cada um?
Organizei em uma tabela os critérios que uso no trabalho.
| Categoria | Abordagem com protocolo | Abordagem com closure |
|---|---|---|
| Situação adequada | Estratégia complexa ou com estado | Estratégia curta e simples |
| Reutilização | Fácil de reutilizar em vários lugares | Boa para uso pontual |
| Quantidade de código | Exige declaração de tipo; maior | Definição no local; menor |
| Testes | Fácil de validar como tipo individual | Validar apenas a lógica de forma simples |
| Legibilidade | A intenção fica clara pelo nome | Clara quando curta, confusa quando longa |
Este é o meu critério.
Se a estratégia possui estado interno, é reutilizada em várias telas ou precisa expressar claramente sua intenção pelo nome, use um protocolo.
Se você só troca na hora uma lógica de uma ou duas linhas, closure é a escolha certa.
Não há problema algum em combinar os dois. Uso protocolos para políticas maiores e closures para opções detalhadas.
Perguntas frequentes (Q&A)
P. Qual é a diferença entre o padrão Strategy e herança (sobrescrita)?
A herança fica vinculada à classe pai, então o comportamento é fixado em tempo de compilação.
Já o padrão Strategy permite trocar estratégias livremente durante a execução e não depende de uma hierarquia de classes.
P. Qual é a diferença em relação a usar o switch de um enum?
Com switch, é preciso abrir e modificar o código existente sempre que surge um novo caso.
Com o padrão Strategy, basta “adicionar” uma nova estratégia, reduzindo a necessidade de alterar o código existente.
No fim, a essência do padrão Strategy é simples: mover as partes variáveis para fora e facilitar sua troca.
Usando os protocolos e closures de hoje conforme a situação, você certamente escapará daquele inferno cansativo de if-else. Comece refatorando um pequeno trecho de código!

