11.5 Polimorfismo
O que guarda uma variável do tipo Funcionario ? Uma referência para um Funcionario , nunca o objeto em si.
Na herança, vimos que todo Gerente é um Funcionario , pois é uma extensão deste. Podemos nos referir a um Gerente como sendo um Funcionario . Se alguém precisa falar com um Funcionario do banco, pode falar com um Gerente ! Porque? Pois Gerente é um Funcionario . Essa é a semântica da herança.
Polimorfismo é a capacidade de um objeto poder ser referenciado de várias formas (cuidado, polimorfismo não quer dizer que o objeto fica se transformando, muito pelo contrário, um objeto nasce de um tipo e morre daquele tipo, o que pode mudar é a maneira como nos referimos a ele).
A situação que costuma aparecer é a que temos um método que recebe um argumento do tipo
Funcionario :
class ControleDeBonificacoes:
python
def __init__(self, total_bonificacoes=0): self._total_bonificacoes = total_bonificacoes
python
def registra(self, funcionario):self._total_bonificacoes += funcionario.get_bonificacao()
@property
def total_bonificacoes(self): return self._total_bonificacoes
E podemos fazer:
java
if name == ' main ':funcionario = Funcionario(‘João’, ‘111111111-11’, 2000.0) print(f’bonificacao funcionario: {} {funcionario.get_bonificacao(}’))
gerente = Gerente(f’José”, “222222222-22”, 5000.0 ‘1234’, 0) print(“bonificacao gerente: {} {gerente.get_bonificacao(}’))
controle = ControleDeBonificacoes() controle.registra(funcionario) controle.registra(gerente)
print(f'total: {} {controle.total_bonificacoes}')que gera a saída:
bonificacao funcionario: 200.0
bonificacao gerente: 1500.0
total: 1700.0
Repare que conseguimos passar um Gerente para um método que “recebe” um Funcionario como argumento. Pense como numa porta na agência bancária com o seguinte aviso: “Permitida a entrada apenas de Funcionários”. Um gerente pode passar nessa porta? Sim, pois Gerente é um Funcionario .
Qual será o valor resultante? Não importa que dentro do método registra() do ControleDeBonificacoes receba Funcionario . Quando ele receber um objeto que realmente é um Gerente , o seu método reescrito será invocado. Reafirmando: não importa como nos referenciamos a um objeto, o método que será invocado é sempre o que é dele.
No dia em que criarmos uma classe Secretaria , por exemplo, que é filha de Funcionario , precisaremos mudar a classe ControleDeBonificacoes ? Não. Basta a classe Secretaria reescrever os métodos que lhe parecerem necessários. É exatamente esse o poder do polimorfismo, juntamente com a reescrita de método: diminuir o acoplamento entre as classes, para evitar que novos códigos resultem em modificações em inúmeros lugares.
Repare que quem criou ControleDeBonificacoes pode nunca ter imaginado a criação da classe Secretaria ou Engenheiro . Contudo, não será necessário reimplementar esse controle em cada nova classe: reaproveitamos aquele código.
Pensar desta maneira em linguagens com tipagem estática é o mais correto, já que as variáveis são tipadas e garantem, através do compilador, que o método só funcionará se receber um tipo Funcionario . Mas não é o que acontece em linguagens de tipagem dinâmica como Python. Vamos supor que temos uma classe para representar os clientes do banco:
class Cliente:
python
def __init__(self, nome, cpf, senha): self._nome = nome
self._cpf = cpf self._senha = senha
# métodos e properties
Nada impede de registrarmos um Cliente em ControleDeBonificacoes . Vamos ver o que acontece:
cliente = (‘Maria’, ‘333333333-33’, ‘1234’)
controle = ControleBonificacoes() controle.registra(cliente)
Saída:
File "
File “<stdin”>, line 67, in regista self._total_bonificacoes += funcionario.get_bonificacao()
AttributeError: ‘Cliente’ object has no attribute ‘get_bonificacao’
Veja que lança um AttibuteError com a mensagem dizendo que Cliente não possui o atributo get_bonificacao . Portanto, aqui não importa se o objeto recebido no método registra() é um Funcionario , mas se ele possui o método get_bonificacao() .
O método registra() utiliza um método da classe Funcionario e, portanto, funcionará com
qualquer instância de uma subclasse de Funcionario ou qualquer instância de uma classe que implemente o método get_bonificacao() .
Podemos evitar este erro verificando se o objeto passado possui ou não um atributo
get_bonificacao() através da função hasattr() :
class ControleDeBonificacoes:
python
def __init__(self, total_bonificacoes=0): self._total_bonificacoes = total_bonificacoes
python
def registra(self, obj): if(hasattr(obj, 'get_bonificacao')):self._total_bonificacoes += obj.get_bonificacao() else:
print('instância de {} não implementa o método get_bonificacao()'.format(self. class .__name ))
demais métodos
A função hasattr() recebe dois parâmetros, o objeto e o atributo (na forma de string ) - e
verifica se o objeto possui aquele atributo, ou seja, se o atributo está contido no dict do objeto.
Então, fazemos a pergunta: get_bonificacao() é atributo de Funcionario ? Se sim, entra no bloco
if e podemos chamar o método tranquilamente, evitando erros.
Agora, se tentarmos chamar o método registra() passando um Cliente recebemos a saída:
‘Cliente’ object has no attribute ‘get_bonificacao
Portanto, o tipo passado para o método registra() não importa aqui, e sim se o objeto passado implementa ou não o método get_bonificacao() . Ou seja, basta que o objeto atenda a um determinado protocolo.
Existe uma função no Python que funciona de forma semelhante mas considera o tipo da instância, é a função isinstance(). Ao invés de passar uma instância, passamos a classe no segundo parâmetro.
class ControleDeBonificacoes:
python
def __init__(self, total_bonificacoes=0):
self. total_bonificacoes = total_bonificacoes
python
def registra(self, obj): if(isinstance(obj, Funcionario)):self. total_bonificacoes += obj.get_bonificacao() else:
print('instância de {} não implementa o método get_bonificacao()'.format(self. class . name ))
Mas essa não é a maneira Pythônica. Você deve escrever o código esperando somente uma interface do objeto, não o tipo dele. A interface é o conjunto de métodos públicos de uma classe. No caso da nossa classe ControleDeBonificacoes , o método registra() espera um objeto que possua o método get_bonificacao() , e não um objeto do tipo Funcionario .