Design de software

Programação orientada a protocolos (POP): superando os limites da OOP

A programação orientada a protocolos (POP) projeta tipos pelo que eles podem fazer, não pelo que herdaram. Este artigo resume, com exemplos em Swift, onde a herança trava, como resolver isso combinando protocolos e quando evitar essa abordagem.

4 min de leitura
Imagem de capa de Programação orientada a protocolos (POP): superando os limites da OOP

Ao ampliar funcionalidades com herança de classes, todos já vimos a classe-pai ficar cada vez mais inchada. Quando continuamos colocando métodos comuns na superclasse, acabamos diante de uma classe-pai enorme, difícil até de tocar.

A programação orientada a protocolos (POP) supera os limites da OOP justamente nesse ponto. POP projeta tipos pelo que eles podem fazer, não pelo que herdaram. Em vez de uma estrutura vertical de herança, combina as capacidades necessárias em protocolos.

Neste artigo, veremos quais limites da OOP deram origem à POP, o que muda no código Swift real e quando usar ou evitar essa abordagem.


Onde a herança da OOP trava?

A herança orientada a objetos é poderosa, mas costuma encontrar obstáculos em três pontos.

Primeiro, a limitação da herança única. As classes Swift só podem ter uma classe-pai. Para criar um tipo que faça rede e cache, apenas a herança não resolve.

Segundo, o problema da classe-base inchada. Ao concentrar funcionalidades comuns no pai, os filhos herdam até métodos que não usam.

Terceiro, a compatibilidade com tipos por valor. struct e enum do Swift não permitem herança, enquanto grande parte da biblioteca padrão do Swift usa tipos por valor.

Ou seja, um design baseado apenas em herança dificulta aproveitar as vantagens dos tipos por valor promovidos pelo Swift.


O que é programação orientada a protocolos (POP)?

A POP se popularizou em 2015, quando a Apple declarou na WWDC que “Swift é uma linguagem orientada a protocolos”.

O ponto central é poder adicionar implementações padrão aos protocolos. Com extensões de protocolo, não definimos apenas a interface: também distribuímos o comportamento real.

Por exemplo:

protocol Greetable {
    var name: String { get }
}

extension Greetable {
    func greet() -> String {
        return "Olá, \(name)sou"
    }
}

struct Person: Greetable {
    let name: String
}

print(Person(name: "Jihun").greet())
// Saída: Olá, sou Jihun

Basta adotar Greetable para receber greet() de graça. Sem herança, também distribuímos a funcionalidade para um struct.

Também é possível adotar vários protocolos ao mesmo tempo. Separando as capacidades de rede e cache em protocolos, cada tipo recebe apenas o que precisa.

Diagrama de classes do tipo Person adotando os protocolos Greetable e Cacheable
Um protocolo, uma capacidade: adicione apenas o necessário

Herança da OOP vs. POP: qual é a diferença?

A diferença pode ser resumida nesta tabela.

Item Herança da OOP POP
Reutilização de código Herdada da classe-pai Combinada por extensões de protocolo
Relação entre tipos Vertical (is-a) Horizontal (can-do)
Adoção múltipla Apenas herança única Adoção simultânea de vários protocolos
Suporte a tipos por valor struct/enum não suportados struct/enum suportados
Acoplamento Pai e filho fortemente ligados Separação flexível por capacidade

Em resumo:

A herança diz “o que você é”; o protocolo diz “o que você pode fazer”.

Tela do Xcode com uma tabela comparando OOP e POP e uma mesa com café
Nessas situações, penso primeiro “dá para separar com protocolos?” em vez de “vamos começar pela herança”

Quando usar POP e quando evitar?

POP não é solução para tudo. Escolha conforme a situação.

Situação Avaliação
Quero compartilhar funcionalidades entre tipos por valor (struct/enum) POP é a resposta
Preciso combinar capacidades diferentes POP é vantajosa
Já existe uma hierarquia clara (animal-mamífero-cão) Herança também funciona bem
Objeto em que compartilhar referências é essencial (ex.: controlador de visualização) Herança de classes é natural
Os protocolos estão fragmentados demais e difíceis de acompanhar Abstração excessiva; revise

O critério é simples: use POP para tipos por valor e combinações de capacidades; herança para hierarquias claras e compartilhamento de referências. Não são opostos, mas ferramentas que podem ser usadas juntas.

É assim que perguntam em entrevistas

P. Que limitações da OOP a POP resolve?

Ela resolve a herança única e o problema das classes-base inchadas. Permite combinar vários protocolos para adicionar apenas as capacidades necessárias e fornecer implementações padrão, por extensões de protocolo, a tipos por valor como struct/enum, que não aceitam herança.

P. Qual é a diferença entre extensão de protocolo e herança de classes?

A herança cria uma relação vertical is-a e permite apenas um pai; protocolos criam uma relação horizontal can-do e podem ser adotados vários ao mesmo tempo. Além disso, a herança só se aplica a classes, que são tipos por referência, enquanto protocolos também se aplicam a tipos por valor.


Não é uma dicotomia entre herança ruim e protocolos bons. Mas, ao desenvolver em Swift, vale pensar primeiro “dá para separar isto com protocolos?” em vez de “vamos começar pela herança”. Esse hábito leva a um código mais flexível.

Continue lendo