Aula 12: O Pilar do Polimorfismo

Muitas formas, um só comportamento.

O Que Você Vai Aprender

Nesta aula, você mergulhará no Pilar do Polimorfismo, o quarto e último pilar da Programação Orientada a Objetos. Entenderá a capacidade de objetos de diferentes classes serem tratados da mesma forma, desde que compartilhem uma superclasse ou um comportamento comum. Você também conhecerá o conceito de "Duck Typing", fundamental em Python.

🦆 Conceito de Polimorfismo

Compreender a ideia de "muitas formas" e como objetos podem se comportar diferentemente com o mesmo método.

🔍 Duck Typing em Python

Aprender como Python foca no comportamento dos objetos, e não no seu tipo explícito.

Conceito: O Pilar do Polimorfismo

O Polimorfismo, do grego "poli" (muitas) e "morfos" (formas), é a capacidade de objetos de diferentes classes responderem ao mesmo nome de método de maneiras distintas, de acordo com sua própria implementação. Isso é possível quando essas classes compartilham uma superclasse comum ou uma interface (um conjunto de métodos).

Polimorfismo Através da Herança e Sobrescrita

A forma mais comum de polimorfismo em linguagens orientadas a objetos, incluindo Python, é alcançada através da herança e da sobrescrita de métodos. Uma superclasse define um método genérico, e suas subclasses fornecem implementações específicas para esse mesmo método.

class Animal:
    def fazer_som(self):
        print("Som genérico de animal")

class Gato(Animal):
    def fazer_som(self): # Sobrescrevendo o método
        print("Miau!")

class Cachorro(Animal):
    def fazer_som(self): # Sobrescrevendo o método
        print("Au Au!")

# Criando uma lista de animais de diferentes tipos
gato = Gato()
cachorro = Cachorro()

animais = [gato, cachorro]

# Iterando sobre a lista e chamando o mesmo método 'fazer_som()'
for animal in animais:
    animal.fazer_som() # O mesmo chamado de método se comporta de forma diferente
# Saída:
# Miau!
# Au Au!

Benefícios do Polimorfismo

  • **Flexibilidade e Generalização:** Você pode escrever código que opera em objetos de uma superclasse, e ele automaticamente funcionará com qualquer subclasse, sem precisar de `if/else` para cada tipo.
  • **Reusabilidade:** Permite criar código mais genérico e reutilizável.
  • **Manutenibilidade:** Facilita a adição de novas classes à hierarquia sem modificar o código existente que já utiliza o polimorfismo.

Duck Typing em Python

Python é uma linguagem de tipagem dinâmica e adota o conceito de Duck Typing. A frase famosa que resume isso é: "Se anda como um pato e faz quack como um pato, então é um pato."

O Que Significa?

Em Python, o que importa não é o tipo explícito de um objeto (se ele é um `Gato` ou um `Cachorro`), mas sim o seu comportamento (quais métodos ele possui e como eles são implementados). Se um objeto tem um método `fazer_som()`, Python não se preocupa se ele herda de `Animal` ou não; ele simplesmente tentará chamar `fazer_som()`.

class Pato:
    def fazer_som(self):
        print("Quack!")
    def nadar(self):
        print("Nadando como um pato.")

class Pessoa:
    def fazer_som(self):
        print("Olá!")
    # Pessoa não tem método 'nadar'

def interagir_com_som(animal):
    # Não importa se 'animal' é um Pato, Gato ou Pessoa,
    # contanto que ele tenha o método 'fazer_som()'
    animal.fazer_som()

pato = Pato()
pessoa = Pessoa()

interagir_com_som(pato)   # Saída: Quack!
interagir_com_som(pessoa) # Saída: Olá!

Implicações do Duck Typing

  • **Mais Flexibilidade:** Permite que você escreva código mais flexível, que funciona com qualquer objeto que se "comporte" de uma certa maneira.
  • **Menos Acoplamento:** As classes não precisam estar rigidamente ligadas por herança para usar o polimorfismo.
  • **Requer Cuidado:** Embora flexível, exige que o programador saiba quais métodos os objetos que ele está usando devem ter, para evitar erros em tempo de execução.

Simulador: Polimorfismo com Veículos

Crie diferentes tipos de veículos e veja como o método `mover()` se comporta de forma polimórfica, mesmo sendo chamado da mesma maneira para todos.

Classes `Veiculo`, `Carro` e `Bicicleta` (simuladas)

class Veiculo:
    def __init__(self, nome):
        self.nome = nome
    
    def mover(self):
        return f"O {self.nome} está se movendo de forma genérica."

class Carro(Veiculo):
    def __init__(self, nome, velocidade_max):
        super().__init__(nome)
        self.velocidade_max = velocidade_max

    def mover(self): # Sobrescrito
        return f"O carro {self.nome} está acelerando a {self.velocidade_max} km/h."

class Bicicleta(Veiculo):
    def __init__(self, nome, tipo_marcha):
        super().__init__(nome)
        self.tipo_marcha = tipo_marcha

    def mover(self): # Sobrescrito
        return f"A bicicleta {self.nome} está pedalando com marcha {self.tipo_marcha}."

# Lista polimórfica:
# veiculos = [Carro("Fusca", 120), Bicicleta("Caloi", "manual")]
# for v in veiculos:
#     print(v.mover())

Teste o Comportamento Polimórfico

Lista de veículos vazia. Adicione alguns!

Desafios para Continuar

Coloque seus conhecimentos em prática com estes desafios para solidificar sua compreensão sobre polimorfismo e Duck Typing.

  • 1. Crie uma Função Polimórfica de Pagamento

    Crie uma superclasse `Pagamento` com um método `processar_pagamento()`. Crie subclasses `CartaoCredito` e `PayPal`, cada uma sobrescrevendo `processar_pagamento()` para exibir uma mensagem específica do método de pagamento.

    Em seguida, crie uma função `realizar_transacao(forma_pagamento)` que receba um objeto de pagamento e chame `forma_pagamento.processar_pagamento()`. Teste a função com instâncias de `CartaoCredito` e `PayPal`.

  • 2. Demonstre Duck Typing com `Voar`

    Crie duas classes: `Pato` e `Aviao`. Ambas as classes devem ter um método `voar()` que imprima uma mensagem específica para cada um (ex: "O pato está voando!" e "O avião está voando!").

    Crie uma função `fazer_voar(objeto)` que receba um objeto e chame `objeto.voar()`. Demonstre que esta função funciona tanto para `Pato` quanto para `Aviao`, apesar de não haver uma superclasse comum `Voador`.