Polimorfismo#
Polimorfismo é um conceito de POO que permite que diferentes classes tenham métodos com o mesmo nome, mas que funcionam de formas distintas. Ou seja, o mesmo método pode ser implementado de maneiras diferentes em várias classes, dependendo do tipo de objeto que o invoca.
É bem simples mesmo: classes diferentes que implementam métodos com mesmo nome que fazem coisas diferentes.
Vamos construir juntos um exemplo prático para explicar este conceito.
Exemplo prático#
Pense em uma plataforma de e-commerce, quando você vai comprar algo. Existem várias formas de pagamento, certo? Pix, boleto, cartão de crédito. Vamos supor as seguintes regras:
Pix: desconto de 10%
Boleto: desconto de 5%
Cartão de crédito: taxa de juros de 2%
São diferentes formas de se fazer a mesma coisa: processar pagamento. Este é o principal caso de uso de polimorfismo.
Vamos desenvolver este exemplo passo a passo.
class CartaoCredito:
def processar_pagamento(self, valor: float) -> None:
taxa = valor * 0.02 # 2% de juros
total = valor + taxa
print(
f"Pagamento de R${total:.2f} processado via cartão de crédito. (Juros: R${taxa:.2f})"
)
Até aqui nenhuma novidade. Por simplicidade, eu sequer implementei o método construtor da classe com atributos. Vamos focar aqui em polimorfismo.
O único ponto que vocês precisam notar é que eu criei um método processar_pagamento
. Este é o método que será polimórfico.
Na sequência, vamos criar outra classe para as outras formas de pagamamento.
class CartaoCredito:
def processar_pagamento(self, valor: float) -> None:
taxa = valor * 0.02 # 2% de juros
total = valor + taxa
print(
f"Pagamento de R${total:.2f} processado via cartão de crédito. (Juros: R${taxa:.2f})"
)
class Boleto:
def processar_pagamento(self, valor: float) -> None:
desconto = valor * 0.05 # 5% de desconto
total = valor - desconto
print(
f"Pagamento de R${total:.2f} processado via boleto bancário. (Desconto: R${desconto:.2f})"
)
class Pix:
def processar_pagamento(self, valor: float) -> None:
desconto = valor * 0.10 # 10% de desconto
total = valor - desconto
print(
f"Pagamento de R${total:.2f} processado via pix. (Desconto: R${desconto:.2f})"
)
Criamos mais 2 classes, Boleto
e Pix
, e ambas tem o método com o mesmo nome processar_pagamento
. Mas, cada uma implementa de uma forma diferente, com suas regras específicas. Por fim, vamos criar um objeto de cada classe e chamar o método processar_pagamento
.
class CartaoCredito:
def processar_pagamento(self, valor: float) -> None:
taxa = valor * 0.02 # 2% de juros
total = valor + taxa
print(
f"Pagamento de R${total:.2f} processado via cartão de crédito. (Juros: R${taxa:.2f})"
)
class Boleto:
def processar_pagamento(self, valor: float) -> None:
desconto = valor * 0.05 # 5% de desconto
total = valor - desconto
print(
f"Pagamento de R${total:.2f} processado via boleto bancário. (Desconto: R${desconto:.2f})"
)
class Pix:
def processar_pagamento(self, valor: float) -> None:
desconto = valor * 0.10 # 10% de desconto
total = valor - desconto
print(
f"Pagamento de R${total:.2f} processado via pix. (Desconto: R${desconto:.2f})"
)
carta_credito = CartaoCredito()
boleto = Boleto()
pix = Pix()
valor_tota_compra = 1_000
carta_credito.processar_pagamento(valor_tota_compra)
boleto.processar_pagamento(valor_tota_compra)
pix.processar_pagamento(valor_tota_compra)
Pagamento de R$1020.00 processado via cartão de crédito. (Juros: R$20.00)
Pagamento de R$950.00 processado via boleto bancário. (Desconto: R$50.00)
Pagamento de R$900.00 processado via pix. (Desconto: R$100.00)
O fato de eu ter chamado o mesmo método processar_pagamento
para objetos de classes diferentes é o que caracteriza o polimorfismo. Dado que os objetos tem métodos com o mesmo nome, podemos começar a brincar com isso, por exemplo, colocando as formas de pagamento em uma lista e usar laço de repetição para averiguar todas as formas de pagamento.
# Ocultei a definição das classes acima CartaoCredito, Boleto e Pix para simplificar o código
valor_tota_compra = 1_000
forma_pagamentos = [CartaoCredito(), Boleto(), Pix()]
for formato in forma_pagamentos:
formato.processar_pagamento(valor_tota_compra)
Pagamento de R$1020.00 processado via cartão de crédito. (Juros: R$20.00)
Pagamento de R$950.00 processado via boleto bancário. (Desconto: R$50.00)
Pagamento de R$900.00 processado via pix. (Desconto: R$100.00)
Só é possível realizar esta operação porque usamos polimorfismo. Todas as classes tem métodos com o mesmo nome que fazem coisas diferentes.
Polimorfismo + herança#
Podemos unir o que aprendemos sobre herança com polimorfismo. Vou explicar de forma bem conceitual e simples com um exemplo didático.
class Animal:
def fazer_barulho(self):
raise NotImplementedError(
"Subclasse precisa implementar o método `fazer_barulho`"
)
class Cachorro(Animal):
def fazer_barulho(self):
return "Au! Au!"
class Cat(Animal):
def fazer_barulho(self):
return "Miau!"
animais = [Cachorro(), Cat()]
for animal in animais:
print(animal.fazer_barulho())
Au! Au!
Miau!
No código didático acima, temos uma superclasse Animal
que tem um método fazer_barulho()
. Porém, a superclasse não implementa este método, apenas define que ele existe. Cada subclasse de Animal
implementa o método fazer_barulho()
de uma forma diferente.
O que acontece é que o método da superclasse fazer_barulho()
é sobrescrito pelas subclasses. É uma forma de garantir que se os métodos não tiverem o mesmo nome, o código vai nos avisar que algo está errado.
Polimorfismo substituindo if-else
#
Um dos benefícios do polimorfismo é que ele pode substituir if-else
. Recuperando o exemplo do e-commerce, poderíamos fazer de outra forma, com if-else
. Mas, o código ficaria menos flexível conforme ele for crescendo. Vejam como seria o código sem polimorfismo (e sem classes também), feito com if-else
:
forma_pagamento = "pix"
valor_tota_compra = 1_000
if forma_pagamento == "cartao_credito":
taxa = valor_tota_compra * 0.02 # 2% de juros
total = valor_tota_compra + taxa
elif forma_pagamento == "boleto":
desconto = valor_tota_compra * 0.05
total = valor_tota_compra - desconto
elif forma_pagamento == "pix":
desconto = valor_tota_compra * 0.10
total = valor_tota_compra - desconto
print(f"Pagamento de R${total:.2f} processado via {forma_pagamento}.")
Pagamento de R$900.00 processado via pix.
Atenção (código menor nem sempre é melhor)
Eu sei, você deve ter lido o código acima e pensado: «Ah, mas o código ficou muito menor e mais simples». Sim, é verdade. Mas, código menor nem sempre é melhor, coloque isso na sua cabeça!
Aqui estamos trazendo um exemplo didático com 3 formas de pagamentos apenas. Imagine um sistema real com 10, 20, 30 classes e com várias outras regras que não apenas forma de pagamento. Isso que nem mostrei sobre if-else
aninhados (um dentro do outro) aqui, que é um verdadeiro pesadelo para entender as regras de negócio. O código com if-else
começaria a crescer de forma exponencial, cheio de regras espalhadas, o que acaba tornando-o difícil de manter e modificar no futuro.
O polimorfismo é a forma mais elegante de simplificar o código (novamente, simples não é menor) e torná-lo mais flexível para futuras mudanças.