Curso Python e Orientação a Objetos > apostila > Capitulo 12
if (hasattr(obj, 'autentica')):
Mas podemos esquecer, no futuro, quando modelarmos a classe Presidente (que também é um funcionário e autenticável), de implementar o método autentica(). Não faz sentido colocarmos o método autentica() na classe Funcionario já que nem todo funcionário é autenticável.
Como Python admite herança múltipla, podemos criar a classe Autenticavel:
Se as classes B e C herdarem a classe A e a classe D herdar as classes B e C, e as classes B e C têm um método m2(), qual método a classe D herda?
Felizmente, a função super() sabe como lidar de forma inteligente com herança múltipla. Se usá-la dentro do método, todos os métodos das superclasses devem ser chamados seguindo o MRO.
Se usarmos herança múltipla, geralmente é uma boa ideia projetarmos nossas classes de uma maneira que evite o tipo de ambiguidade descrita acima - apesar do Python possuir o MRO, em sistemas grandes a herança múltipla ainda pode causar muitos problemas.
Por exemplo, nossa classe Autenticavel pode ser um mix-in, já que ela existe apenas para acrescentar a funcionalidade de ser autenticável, ou seja, para herdar seu método autentica.
Repare que nossos mix-ins não têm um método __init__() . Muitos mix-ins apenas fornecem métodos adicionais, mas não inicializam nada. Isso às vezes significa que eles dependem de outras propriedades que já existem em suas filhas. Cada mix-in é responsável por fornecer uma peça específica de funcionalidade opcional - é um jeito de compor classes.
Essa é uma das abordagens de se usar herança múltipla, mas ela é bastante desencorajada. Caso você utilize, opte por mix-ins sabendo de suas desvantagens. Usando em sistemas grandes, podem ocorrer colisões com nomes de métodos, métodos substituídos acidentalmente, hierarquia de classes pouco clara e dificuldade de ler e entender classes compostas por muitos mix-ins, dentre outras desvantagens. O problema da herança múltipla permanece.
Outra abordagem possível é definir funções fora de classes, digamos em um módulo e fazer chamadas dessas funções passando nossos objetos. Mas isso é um afastamento radical do paradigma orientado a objetos, que é baseada em métodos definidos dentro das classes.
Para sabe mais - Tkinter
A imagem acima foi retirada do livro Python Fluente de Luciano Ramalho - mostrando parte do complicado modelo de classes utilizando herança múltipla do pacote Tkinter. A setas representam o MRO que deve iniciar na classe Text. A classe Text implementa um campo de texto editável e tem muitas funcionalidades próprias, além de herdar muitos métodos de outras classes.
Exercícios - Mix-Ins
Nosso banco precisa tributar dinheiro de alguns bens que nossos clientes possuem. Para isso vamos criar uma classe Tributavel com um método que devolve o imposto sobre a conta:
Lemos essa classe da seguinte maneira: "Todos que quiserem ser tributáveis precisam saber retornar o valor do imposto". Alguns bens são tributáveis e outros não. Por exemplo: ContaPoupanca não é tributável, já para ContaCorrente você precisa pagar 1% da conta e o SeguroDeVida tem uma faixa fixa de 50 reais mais 5% do valor do seguro.
Transforme a classe Tributavel em um mix-in:
Faça a classe ContaCorrente herdar da classe TributavelMixIn e implemente o método "exigido" pelo MixIn:
Crie a classe SeguroDeVida que vai herdar de TributavelMixIn. Crie seus respectivos atributos e implemente o método do MixIn:
Vamos criar a classe ManipuladorDeTributaveis em um arquivo chamado manipulador.py. Essa classe deve ter um método chamado calcula_impostos() que recebe uma lista de tributáveis e retorna o total de impostos cobrados:
Vimos que herança múltipla pode ser perigosa, e se nosso sistema crescer pode gerar muita confusão e conflito de nomes de métodos. Uma maneira mais eficaz nestes casos é usar classes abstratas como interfaces, que veremos a seguir.
O Python não possui uma palavra reservada interface. Mesmo sem uma palavra reservada para a mesma, toda classe possui uma interface. Interfaces são os atributos públicos definidos (que em Python são tanto atributos quanto métodos) em uma classe - isso inclui os métodos especiais como __str__() e __add__().
Uma interface vista como um conjunto de métodos para desempenhar um papel é o que os programadores da SmallTalk chamavam de protocolo, e este termo foi disseminado em comunidades de programadores de linguagens dinâmicas. Esse protocolo funciona como um contrato.
O método __str__(), por exemplo, é esperado que retorne uma representação do objeto em forma de string. Nada impede de fazermos outras coisas dentro do método como deletar algum conteúdo ou fazer algum cálculo; ao invés de retornarmos apenas a string. Mas há um entendimento prévio comum do que este método deve fazer e está presente na documentação do Python. Este é um exemplo onde o contrato semântico é descrito em um manual. Algumas linguagens de tipagem estática, como Java, possuem interfaces em sua biblioteca padrão e podem garantir este contrato em tempo de compilação.
A partir do Python 2.6, a definição de interfaces utilizando o módulo ABC é uma solução mais elegante do que os mix-ins. Nossa classe Autenticavel pode ser uma classe abstrata com o método abstrato autentica():
""" Método abstrato que faz verificação da senha.
Devolve True se a senha confere, e False caso contrário.
if (isinstance(d, Autenticavel)):
if (isinstance(obj, Autenticavel)):
Dessa maneira fugimos da herança múltipla e garantimos um contrato, um protocolo. Classes abstratas complementam o duck typing, provendo uma maneira de definir interfaces quando técnicas como usar hasattr() são ruins ou sutilmente erradas. Você pode ler mais a respeito no documento da PEP que introduz classes abstratas - é a PEP 3119 e você pode acessar seu conteúdo neste link: https://www.python.org/dev/peps/pep-3119/
(Opcional) Exercícios - Interfaces e classes Abstratas
Nosso banco precisa tributar dinheiro de alguns bens que nossos clientes possuem. Para isso, vamos criar uma classe Tributavel no módulo tributavel.py, e adicionar um método que devolve o imposto sobre a conta:
Lemos essa classe da seguinte maneira: "todos que quiserem ser tributáveis precisam saber retornar o valor do imposto". Alguns bens são tributáveis e outros não, ContaPoupanca não é tributável, já para ContaCorrente você precisa pagar 1% da conta e o SeguroDeVida tem uma faixa fixa de 50 reais mais 5% do valor do seguro.
Nada impede que os usuários de nossa classe tributavel implementem o método get_valor_imposto de maneira não esperada por nós. Então, vamos acrescentar a documentação utilizando docstring que aprendemos no capítulo de módulos:
Faça a classe ContaCorrente herdar da classe Tributavel:
Crie a classe SeguroDeVida, com os atributos valor, titular e numero_apolice, que também deve ser um tributável. Implemente o método get_valor_imposto() de acordo com a regra de negócio definida pelo exercício:
Vamos criar a classe ManipuladorDeTributaveis em um arquivo chamado _manipuladortributaveis.py. Essa classe deve ter um método chamado calcula_impostos() que recebe umas lista de tributáveis e retorna o total de impostos cobrados:
Nossas classes ContaCorrente e SeguroDeVida já implementam o método get_valor_imposto(). Vamos instanciar cada umas delas e testar a chamada do método:
Nosso código funciona, mas ainda estamos utilizando herança múltipla! Vamos melhorar nosso código. Faça com que ContaCorrente e SeguroDeVida não mais herdem da classe Tributavel:
Vamos registrar nossas classes ContaCorrente e SeguroDeVida como subclasses virtuais de Tributavel, de modo que funcione como uma interface.
if (isinstance(t, Tributavel)):
(Opcional) Agora, além de ContaCorrente e SeguroDeVida, nossa ContaInvestimento também deve ser um tributável, cobrando 3% do saldo. Instancie uma ContaInvestimento.
class ContaInvestimento(Conta):
return self._saldo * 0.03
Registre a classe ContaInvestimento como tributável. Adicione a ContaInvestimento criada na lista de tributáveis do exercício anterior e calcule o total de impostos através do ManipuladorDeTributaveis.
ci = ContaInvestimento('Ana', '123-0')
ci.deposita(100.0)
Tributavel.register(ContaInvestimento)
lista_tributaveis.append(ci)
Neste capítulo, aprendemos sobre herança múltipla e suas desvantagens mesmo utilizando mix-ins. Além disso, aprendemos a utilizar classes abstratas como interfaces registrando as classes e evitando os problemas com a herança múltipla. Agora nossa classe abstrata Tributavel funciona como um protocolo. No capítulo sobre o módulo collections, veremos na prática alguns conceitos vistos neste capítulo.