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.

⚒️👷 (WIP) capítulo em construção ⚒️👷#