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 "", line 99, in controle.registra(cliente)

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 .


⬅️ Capítulo Anterior | Próximo Capítulo ➡️