Você já ficou preso a um código de API legada que não podia apagar, mas também não conseguia usar do jeito que estava?
É um obstáculo comum ao conectar um módulo de rede antigo a uma tela nova.
Indo direto ao ponto, a solução mais limpa é envolver a API legada em um protocolo (interface) e colocar um adaptador entre os dois.
O padrão Adapter insere um “conversor” entre duas interfaces incompatíveis.
Se você confunde esse padrão com Facade, Proxy e Decorator, que também envolvem objetos, Comparação de 4 padrões de encapsulamento ajuda a diferenciá-los pelo objetivo.
Hoje vou mostrar como aplicar isso em Swift, seguindo o fluxo que vivenciei na prática.
Três coisas que você vai aprender
Para quem está com pressa, aqui vai um resumo primeiro.
- Definir primeiro, em um protocolo, o formato que o código novo precisa
- Criar um tipo adaptador que converta a API legada para esse protocolo
- Fazer telas e view models dependerem apenas do protocolo, não da API legada
Seguindo só essas três regras, o escopo das alterações diminui muito quando você precisar substituir a API inteira.
Depois de mudar para essa estrutura, escrever testes ficou muito mais fácil para mim.
Por que precisamos do padrão Adapter em Swift?
As APIs legadas geralmente não têm o formato que queremos.
Elas podem ser baseadas em callbacks, ter parâmetros confusos ou retornar tipos ambíguos.
Se o código novo se curva às limitações de uma API antiga, a base inteira treme no dia em que essa API desaparece.
Por isso colocamos um adaptador no meio.
Vamos supor que o módulo antigo seja parecido com isto.
// Código legado difícil de mexer API (Baseado em callback)
class LegacyUserAPI {
func fetch(id: Int,
done: @escaping (NSDictionary?) -> Void) {
// Uma chamada de rede antiga...
}
}
Se você levar NSDictionary diretamente até a tela, trocar essa API depois exigirá desmontar também o código da tela.
Dá para ver que não é algo que você queira conectar diretamente.
Como envolver uma API legada em um protocolo (3 etapas)
O processo de encapsulamento é mais simples do que parece.
Etapa 1: defina o formato desejado em um protocolo.
Primeiro, descreva o formato que permite ao código novo dizer: “É assim que quero chamar”.
// A interface limpa que o código novo precisa
protocol UserRepository {
func user(id: Int) async throws -> User
}
Troquei callbacks por async/await e NSDictionary por um tipo User.
Etapa 2: o adaptador ajusta a API legada a este protocolo.
Deixe todas as conversões complicadas confinadas ao adaptador.
// Um adaptador que converte a API legada para o novo protocolo
struct LegacyUserAdapter: UserRepository {
let legacy = LegacyUserAPI()
func user(id: Int) async throws -> User {
try await withCheckedThrowingContinuation { cont in
legacy.fetch(id: id) { dict in
cont.resume(returning: User(dict))
}
}
}
}
O withCheckedThrowingContinuation que converte callbacks em async exerce aqui o papel principal.
Etapa 3: faça a tela depender apenas do protocolo.
O view model não precisa conhecer LegacyUserAPI. Basta conhecer UserRepository.
Assim, o código legado aparece em um único lugar: o adaptador.
O que muda ao usar um adaptador? (Comparação com chamadas diretas)
Comparei em uma tabela as chamadas diretas com a abordagem do adaptador.
| Item | Chamada direta da API legada | Envolver com um adaptador |
|---|---|---|
| Escopo das alterações ao substituir a API | Tela inteira | Um único adaptador |
| Teste unitário | Difícil | Fácil com mock |
| Legibilidade do código novo | Baixa | Alta |
| Esforço inicial | Baixo | Aumenta um pouco |
É verdade que o esforço inicial aumenta um pouco.
Mas, para mim, esse custo valeu totalmente a pena.
Nos testes, basta injetar um objeto falso que implemente UserRepository para validar a lógica da tela sem rede.
Conseguimos começar o desenvolvimento mesmo antes de a API real ficar pronta.
Perguntas frequentes (Q&A)
P. Devo criar o adaptador como struct ou class?
Se não houver estado, struct é suficiente.
Se precisar manter o objeto legado ou compartilhar referências, use class.
P. Qual é a diferença entre os padrões Adapter e Facade?
O objetivo do Adapter é “compatibilizar interfaces”.
O Facade busca “apresentar várias partes complexas como uma coisa simples”, então a direção é um pouco diferente.
P. Como devo nomear o protocolo?
Não siga o nome legado; escolha o nome com base no papel que o código novo precisa.
Por exemplo, use UserRepository em vez de LegacyUserAPI.
Não tente apagar a API legada à força; comece isolando-a discretamente com um protocolo e um adaptador.
Ao confinar o código antigo a um único lugar, a próxima refatoração fica muito mais fácil. Experimente envolver primeiro a API mais complicada do seu projeto.

