Pular para o conteúdo

    Capítulo 28, Intermediário

    Herança e polimorfismo

    Herança reaproveita comportamento e polimorfismo permite tratar tipos diferentes do mesmo jeito. Eu mostro como usar, e principalmente quando preferir composição.

    Código deste capítulo: intermediario/cap28_heranca.py

    Herdar e sobrescrever

    Uma classe filha recebe tudo da classe pai e pode sobrescrever o que quiser. O resultado é o polimorfismo: o mesmo código chama o mesmo nome de método e cada objeto responde do seu jeito:

    intermediario/cap28_heranca.pylinhas 10 a 32
    class Animal:
        def __init__(self, nome):
            self.nome = nome
    
        def falar(self):
            return "..."
    
        def apresentar(self):
            return f"{self.nome} diz {self.falar()}"
    
    
    class Cachorro(Animal):
        def falar(self):
            return "au"
    
    
    class Gato(Animal):
        def falar(self):
            return "miau"
    
    
    for animal in [Cachorro("Rex"), Gato("Mia"), Animal("Ser")]:
        print(animal.apresentar())
    
    Saída
    Rex diz au
    Mia diz miau
    Ser diz ...
    

    O apresentar está escrito uma vez e funciona para todos, porque chama self.falar(), e o self decide qual versão rodar.

    super(): reaproveitar o pai

    Quando a filha precisa estender o comportamento do pai, em vez de substituí-lo, ela chama o pai com super():

    intermediario/cap28_heranca.pylinhas 37 a 56
    class Funcionario:
        def __init__(self, nome, salario):
            self.nome = nome
            self.salario = salario
    
        def bonus(self):
            return self.salario * 0.1
    
    
    class Gerente(Funcionario):
        def __init__(self, nome, salario, equipe):
            super().__init__(nome, salario)
            self.equipe = equipe
    
        def bonus(self):
            return super().bonus() + 100 * len(self.equipe)
    
    
    g = Gerente("Ana", 5000, ["Bia", "Caio"])
    print(g.bonus())
    
    Saída
    700.0
    

    Esquecer o super().__init__(...) é um erro clássico: o pai nunca inicializa os seus atributos, e o erro só aparece mais tarde.

    Hierarquias e a ordem de busca

    Herança pode ter vários níveis. A ordem em que o Python procura um método chama-se MRO (method resolution order) e pode ser consultada:

    intermediario/cap28_heranca.pylinhas 61 a 74
    class A:
        pass
    
    
    class B(A):
        pass
    
    
    class C(B):
        pass
    
    
    print(issubclass(C, A), isinstance(C(), B))
    print([k.__name__ for k in C.__mro__])
    
    Saída
    True True
    ['C', 'B', 'A', 'object']
    

    A herança múltipla (class D(B, C)) existe e funciona, mas é fonte de complexidade. Eu a trato com mais detalhe no nível avançado.

    Duck typing

    Python não exige uma hierarquia para o polimorfismo funcionar. Se o objeto tem o método que você chama, ele serve: "se anda como pato e grasna como pato, é pato":

    intermediario/cap28_heranca.pylinhas 79 a 90
    class Pato:
        def falar(self):
            return "quack"
    
    
    class Robo:
        def falar(self):
            return "bip"
    
    
    for coisa in (Pato(), Robo()):
        print(coisa.falar())
    
    Saída
    quack
    bip
    

    Composição antes de herança

    Herança significa "é um". Composição significa "tem um". Uma Carro é um veículo, mas tem um motor. Eu só herdo quando a relação "é um" é verdadeira e a filha pode ser usada em qualquer lugar onde o pai é esperado. Para todo o resto, componho:

    intermediario/cap28_heranca.pylinhas 95 a 108
    class Motor:
        def ligar(self):
            return "motor ligado"
    
    
    class Carro:
        def __init__(self):
            self.motor = Motor()
    
        def partir(self):
            return self.motor.ligar()
    
    
    print(Carro().partir())
    
    Saída
    motor ligado
    

    Por que eu prefiro composição

    Herança acopla a filha aos detalhes internos do pai: mudar o pai pode quebrar todas as filhas. Composição acopla apenas à interface pública. Quando em dúvida, comece com composição. Dá para evoluir para herança depois, mas desfazer uma hierarquia é caro.

    Exercício 1

    Formas geométricas

    Crie Forma com o método area() que levanta NotImplementedError, e as subclasses Retangulo(largura, altura) e Circulo(raio). Escreva area_total(formas).