Design de software

Polimorfismo em OOP: de overriding a protocolos

Já ficou confuso tentando entender de uma vez o polimorfismo em OOP, do overriding aos protocolos?

4 min de leitura
Imagem de capa de Polimorfismo em OOP: de overriding a protocolos

Já ficou confuso tentando entender de uma vez o polimorfismo em OOP, do overriding aos protocolos?

Overloading, overriding e protocolos podem parecer semelhantes, então é fácil confundi-los.

Vamos direto à conclusão: polimorfismo é quando “uma chamada com o mesmo nome se comporta de forma diferente dependendo do alvo”. O overriding implementa isso por herança, enquanto os protocolos implementam por meio de um contrato.

Neste artigo, vou explicar passo a passo como esses três conceitos se relacionam.

O que exatamente é polimorfismo?

Polimorfismo (Polymorphism) vem do grego e significa “muitas (poly) formas (morph)”.

Em termos simples, é a capacidade de objetos diferentes responderem de formas diferentes à mesma mensagem.

Por exemplo, mesmo recebendo a mesma ordem, o cachorro late e o gato mia quando você diz “faça um som”.

Quem chama só precisa invocar 소리내(). Cada objeto decide o que vai produzir.

O ponto central do polimorfismo é que “quem chama não precisa saber”.

Assim, qualquer objeto pode ser tratado com o mesmo código.

Isso deixa o código muito mais flexível. Adicionar um novo animal não exige alterar o código de chamada.


Overloading e overriding: qual é a diferença?

Os nomes parecidos são o principal motivo da confusão.

Overloading é criar várias funções com o mesmo nome, alterando apenas os parâmetros.

Overriding é quando uma classe filha redefine e sobrescreve uma função criada pela classe pai.

Como só falar pode ser difícil, vamos ver isso no código.

class Animal {
    func sound() { print("...") }
}

class Dog: Animal {
    override func sound() { print("Au au") }  // Overriding
}

let a: Animal = Dog()
a.sound()
// Saída: Au au

O interessante é que, mesmo que o tipo de a seja Animal, ele produz o som de Dog.

Esse é o polimorfismo decidido em tempo de execução ao observar o objeto real.

Uma classe pai cujos filhos redefinem o comportamento de maneiras diferentes
Uma classe pai cujos filhos redefinem o comportamento de maneiras diferentes

Overloading é um pouco diferente. A função a ser chamada é escolhida em tempo de compilação.

func add(_ a: Int, _ b: Int) -> Int { a + b }
func add(_ a: String, _ b: String) -> String { a + b }

print(add(1, 2))       // Saída: 3
print(add("a", "b"))  // Saída: ab

Por isso, overloading também é chamado de polimorfismo estático, e overriding, de polimorfismo dinâmico.


Por que precisamos de protocolos?

A herança já permite polimorfismo, então por que os protocolos existem?

Herança representa uma relação “is-a”, então só pode haver uma classe pai. É difícil um gato ser um animal e, ao mesmo tempo, pertencer a outra hierarquia.

Um protocolo é muito mais flexível: qualquer tipo pode participar se cumprir o contrato.

protocol Soundable {
    func sound()
}

struct Cat: Soundable {
    func sound() { print("Miau") }
}

struct Car: Soundable {
    func sound() { print("Buzina") }
}

let things: [Soundable] = [Cat(), Car()]
things.forEach { $0.sound() }
// Saída: Miau
// Saída: Buzina

Um gato e um carro não têm nenhuma relação de herança. Ainda assim, podem ficar no mesmo array e ser tratados da mesma forma.

Obter polimorfismo sem uma árvore de herança: esse é o poder dos protocolos.

É também por isso que linguagens como Swift priorizam protocolos em vez de herança.

Testei o código na prática e entendi melhor protocol e override
Testei o código na prática e entendi melhor protocol e override

Quando usar herança e quando usar protocolos

Resumi em uma tabela os critérios que uso na prática.

Situação Decisão
Quero herdar código comum Herança (overriding)
Não há relação, mas quero garantir o mesmo comportamento Protocolo
Quero atribuir várias características ao mesmo tempo Protocolo (é possível adotar vários)
Quero dar polimorfismo a um tipo por valor (struct) Protocolo

Basta lembrar alguns princípios simples.

  • Use herança quando quiser reutilizar boa parte da implementação do pai.
  • Use um protocolo quando quiser definir uma “capacidade”.
  • Se a extensibilidade for uma preocupação, considere primeiro os protocolos.

É assim que perguntam em entrevistas

Q. Explique a diferença entre overloading e overriding.

Overloading define parâmetros diferentes com o mesmo nome e é decidido em tempo de compilação. Overriding é quando uma classe filha redefine um método herdado, com decisão em tempo de execução baseada no objeto real. Por isso, o primeiro é chamado de polimorfismo estático e o segundo, de polimorfismo dinâmico.

Q. Quais são as vantagens de usar protocolos em vez de herança?

A herança é limitada a uma classe pai e não pode ser usada com tipos por valor, enquanto um tipo pode adotar vários protocolos, que também podem ser aplicados a structs. Até tipos sem relação podem ser agrupados e tratados pelo mesmo contrato, o que favorece a extensão.


O polimorfismo pode parecer abstrato no início, mas, se você guardar uma frase — “quem chama não precisa saber” —, o restante fica muito mais claro.

Experimente no código o overriding e os protocolos que você aprendeu hoje. Eles vão ficar muito mais naturais. Boa sorte!

Continue lendo