Herança
Herança é um dos principais conceitos da Programação Orientada a Objetos e fornece um mecanismo elegante para evitar repetição de código.
Introdução
Tecnicamente, você já tem utilizado herança no seu código Python desde que criou a primeira classe. Isso mesmo. Ainda que você não expresse isso no código, o Python considera que, implicitamente, sua classe herda da classe object
.
A sintaxe para declarar herança em python é:
- "class"
- nome da classe
- "("
- nome da classe mais geral
- ")"
- ":"
Ou seja, a sintaxe é semelhante à da declaração de uma classe. Veja o código a seguir:
class Pessoa(object):
pass
Nesse exemplo a classe Pessoa
herda da classe object
. Lembre-se que isso é o mesmo que fazer:
class Pessoa:
pass
Vamos a um exemplo mais prático.
Exemplo 1: Pessoa, Professor e Aluno
class Pessoa:
def __init__(self, nome):
self.nome = nome
class Professor(Pessoa):
pass
class Aluno(Pessoa):
pass
A herança que indica Aluno
é uma Pessoa
e Professor
é uma Pessoa
permite o código a seguir:
jose = Professor('José')
print(jose.nome)
A saída da execução do programa é "José"
. Por que isso acontece? Veja:
Professor
é subclasse dePessoa
Professor
herda atributos e métodos dePessoa
(inclusive o construtor)- o código
Professor('José')
cria uma instância deProfessor
, usando o construtor da superclassePessoa
- o código
print(jose.nome)
imprime o valor do atributonome
, presente na classeProfessor
por causa da herança (declarado explicitamente na classePessoa
)
Exemplo 2: Empresa
Esse exemplo demonstra o seguinte contexto:
- Pessoa possui: nome
- Funcionário possui: matrícula e, obrigatoriamente, está relacionado a uma pessoa
- Funcionário Administrativo é um tipo de funcionário e possui: cargo
- Funcionário da Diretoria é um tipo de funcionário é possui: diretoria
- Empresa possui: funcionários (opcionalmente)
- Empresa pode contratar uma pessoa para exercer uma função e, opcionalmente, trabalhar em uma diretoria
- Empresa pode imprimir um relatório dos funcionários
No caso da contratação de uma pessoa:
- se a função for "Diretor", então está sendo contratado um Funcionário da Diretoria
- senão, está sendo contratado um Funcionário Administrativo
A seguir, um trecho de código demonstrando o uso de herança:
class Pessoa:
def __init__(self, nome):
self.nome = nome
def __str__(self):
return self.nome
class Funcionario:
...
class FuncionarioAdministrativo(Funcionario):
...
class FuncionarioDaDiretoria(Funcionario):
...
class Empresa:
...
O trecho de código mostra que:
FuncionarioAdministrativo
herda deFuncionario
FuncionarioDaDiretoria
herda deFuncionario
A seguir, o código-fonte da classe Funcionario
:
class Funcionario:
def __init__(self, matricula, pessoa):
self.matricula = matricula
self.pessoa = pessoa
def __str__(self):
return '[{}] {}'.format(self.matricula, self.pessoa.nome)
Agora o código das classes FuncionarioAdministrativo
e FuncionarioDaDiretoria
:
class FuncionarioAdministrativo(Funcionario):
def __init__(self, matricula, pessoa, cargo):
self.matricula = matricula
self.pessoa = pessoa
self.cargo = cargo
class FuncionarioDaDiretoria(Funcionario):
def __init__(self, matricula, pessoa, diretoria):
self.matricula = matricula
self.pessoa = pessoa
self.diretoria = diretoria
Atenção para os métodos construtores dessas classes:
- o construtor da classe
Funcionario
recebe dois parâmetros:matricula
epessoa
, nesta ordem. Além disso, esse construtor é responsável por criar dois atributos para a classe (matricula
epessoa
) - o construtor da classe
FuncionarioAdministrativo
recebe três parâmetros:matricula
,pessoa
ecargo
e também cria atributos com os mesmos nomes - o construtor da classe
FuncionarioDaDiretoria
recebe três parâmetros:matricula
,pessoa
ediretoria
e também cria atributos com os mesmos nomes
Em outras palavras as subclasses, embora herdem o construtor da superclasse, fornecem implementações próprias dos seus construtores. Isso introduz um conceito importante, visto a seguir.
Sobrescrita de métodos
Sobrescrita (ou reescrita) é um recurso de programação orientada a objetos que permite que subclasses modifiquem métodos herdados.
No Exemplo 2 é isso o que acontece. Como o construtor do Python define a estrutura (os atributos) das instâncias da classe a interpretação é que instâncias de Funcionario
possuem dois atributos (matricula
e pessoa
), enquanto instâncias de FuncionarioAdministrativo
possuem três (matricula
, pessoa
, cargo
), da mesma forma que FuncionarioDaDiretoria
(matricula
, pessoa
, diretoria
). A restrição para usar sobrescrita é que o nome do método precisa ser o mesmo (nesse caso, o construtor).
Embora o código do Exemplo 2 seja simples, ele também mostra que há uma repetição de linhas de código na superclasse e nas subclasses. Precisamos de um recurso para evitar essa repetição de código. Sobrescrita também permite que uma subclasse acesse a implementação da superclasse, como mostra o trecho de código a seguir:
class FuncionarioAdministrativo(Funcionario):
def __init__(self, matricula, pessoa, cargo):
super().__init__(matricula, pessoa)
self.cargo = cargo
class FuncionarioDaDiretoria(Funcionario):
def __init__(self, matricula, pessoa, diretoria):
super().__init__(matricula, pessoa)
self.diretoria = diretoria
A parte importante desse código é a linha:
super().__init__(matricula, pessoa)
O método super()
é a forma do Python implementar o recurso de acessar a superclasse. Na prática, super()
fornece uma referência à superclasse. Dessa forma, o código está utilizando o construtor da superclasse.
O exemplo a seguir mostra outra forma de utilizar esse recurso:
Exemplo 3: Empresa (baseado no Exemplo 2)
class Funcionario:
def __init__(self, matricula, pessoa):
self.matricula = matricula
self.pessoa = pessoa
def __str__(self):
return '[{}] {}'.format(self.matricula, self.pessoa.nome)
class FuncionarioAdministrativo(Funcionario):
def __init__(self, matricula, pessoa, cargo):
super().__init__(matricula, pessoa)
self.cargo = cargo
def __str__(self):
return '[{}] {} ({})'.format(self.matricula, self.pessoa.nome, self.cargo)
class FuncionarioDaDiretoria(Funcionario):
def __init__(self, matricula, pessoa, diretoria):
super().__init__(matricula, pessoa)
self.diretoria = diretoria
def __str__(self):
return '[{}] {} - Diretoria: {}'.format(self.matricula, self.pessoa.nome, self.diretoria)
Dessa vez a sobrescrita continua sendo utilizada no método __str__()
das subclasses, mas sem usar uma referência à superclasse.
Exemplo 4: Empresa (baseado nos exemplos 2 e 3)
Este exemplo demonstra um código mais completo, utilizando ainda outros recursos do Python.
class Pessoa:
def __init__(self, nome):
self.nome = nome
def __str__(self):
return self.nome
class Funcionario:
def __init__(self, matricula, pessoa):
self.matricula = matricula
self.pessoa = pessoa
def __str__(self):
return '[{}] {}'.format(self.matricula, self.pessoa.nome)
class FuncionarioAdministrativo(Funcionario):
def __init__(self, matricula, pessoa, cargo):
super().__init__(matricula, pessoa)
self.cargo = cargo
def __str__(self):
return '[{}] {} ({})'.format(self.matricula, self.pessoa.nome, self.cargo)
class FuncionarioDaDiretoria(Funcionario):
def __init__(self, matricula, pessoa, diretoria):
super().__init__(matricula, pessoa)
self.diretoria = diretoria
def __str__(self):
return '[{}] {} - Diretoria: {}'.format(self.matricula, self.pessoa.nome, self.diretoria)
class Empresa:
def __init__(self):
self.funcionarios = []
def contratar(self, pessoa, funcao, diretoria=None):
matricula = len(self.funcionarios) + 1
funcionario = None
if funcao == 'Diretor':
funcionario = FuncionarioDaDiretoria(
matricula, pessoa, diretoria)
else:
funcionario = FuncionarioAdministrativo(
matricula, pessoa, 'Administrativo I')
self.funcionarios.append(funcionario)
print('Funcionário contratado.')
print('Seja bem-vindo(a), {}!'.format(funcionario))
def imprimir_relatorio_funcionarios(self):
largura = 80
print('=' * largura)
print('** Relatório de funcionários **'.center(largura))
largura_matricula = int(.15 * largura)
largura_nome = int(.5 * largura)
largura_funcao = int(.3 * largura)
print('+{}+{}+{}+'.format('-' * largura_matricula,
'-' * largura_nome, '-' * largura_funcao))
print('|{:^{lm}}|{:^{ln}}|{:^{lf}}|'.format('Matricula', 'Nome', 'Função/Diretoria',
lm=largura_matricula, ln=largura_nome, lf=largura_funcao))
print('+{}+{}+{}+'.format('-' * largura_matricula,
'-' * largura_nome, '-' * largura_funcao))
for funcionario in self.funcionarios:
if isinstance(funcionario, FuncionarioAdministrativo):
print('|{:>{lm}}|{:<{ln}}|{:<{lf}}|'.format(
funcionario.matricula, funcionario.pessoa.nome, funcionario.cargo,
lm=largura_matricula, ln=largura_nome, lf=largura_funcao))
else:
print('|{:>{lm}}|{:<{ln}}|{:<{lf}}|'.format(
funcionario.matricula, funcionario.pessoa.nome, funcionario.diretoria,
lm=largura_matricula, ln=largura_nome, lf=largura_funcao))
print('+{}+{}+{}+'.format('-' * largura_matricula,
'-' * largura_nome, '-' * largura_funcao))
''' teste do modelo de objetos '''
e1 = Empresa()
jose = Pessoa('José')
maria = Pessoa('Maria')
paulo = Pessoa('Paulo')
joana = Pessoa('Joana')
e1.contratar(jose, 'Administrativo')
e1.contratar(maria, 'Diretor', 'Recursos Humanos')
e1.contratar(paulo, 'Diretor', 'Financeiro')
e1.contratar(joana, 'Administrativo')
e1.imprimir_relatorio_funcionarios()
A saída do código seria a seguinte:
Funcionário contratado.
Seja bem-vindo(a), [1] José (Administrativo I)!
Funcionário contratado.
Seja bem-vindo(a), [2] Maria - Diretoria: Recursos Humanos!
Funcionário contratado.
Seja bem-vindo(a), [3] Paulo - Diretoria: Financeiro!
Funcionário contratado.
Seja bem-vindo(a), [4] Joana (Administrativo I)!
================================================================================
** Relatório de funcionários **
+------------+----------------------------------------+------------------------+
| Matricula | Nome | Função/Diretoria |
+------------+----------------------------------------+------------------------+
| 1|José |Administrativo I |
| 2|Maria |Recursos Humanos |
| 3|Paulo |Financeiro |
| 4|Joana |Administrativo I |
+------------+----------------------------------------+------------------------+
Nesse exemplo a impressão do relatório utiliza recursos de formatação de string fornecidos pelo método format()
.
Exemplo 5: lava a jato
Nesse contexto temos:
- Lavajato possui: uma lista de atendimentos (obrigatório)
- Lavajato atende um veículo para realizar um serviço (faz um atendimento)
- Lavajato executa o próximo serviço
- Atendimento possui: serviço e veículo
- Veículo possui: cor e placa
- Motocicleta é um tipo de Veículo
- Carro de Passeio é um tipo de Veículo
- Utilitário Esportivo é um tipo de Veículo
- Caminhonete é um tipo de Veículo
- Caminhão é um tipo de Veículo
O código a seguir fornece uma implementação em Python:
class Veiculo:
def __init__(self, cor, placa):
self.cor = cor
self.placa = placa
def __str__(self):
return '({}, {}, {})'.format(type(self).__name__, self.cor, self.placa)
class Motocicleta(Veiculo):
pass
class CarroDePasseio(Veiculo):
pass
class UtilitarioEsportivo(Veiculo):
pass
class Caminhonete(Veiculo):
pass
class Caminhao(Veiculo):
pass
class Atendimento:
def __init__(self, servico, veiculo):
self.servico = servico
self.veiculo = veiculo
def __str__(self):
return '{}: {}'.format(self.servico, self.veiculo)
class Lavajato:
def __init__(self):
self.atendimentos = []
def atender(self, servico, veiculo):
a = Atendimento(servico, veiculo)
self.atendimentos.append(a)
preco = 30.0
print('Ow, tem cliente pra atender! {}. Vai custar R$ {}, blz?'.format(a, preco))
def executar_proximo_servico(self):
if len(self.atendimentos) == 0:
print('Já atendemos todo mundo. Só diboas...')
else:
proximo = self.atendimentos[0]
self.atendimentos.remove(proximo)
if len(self.atendimentos) == 0:
print('{} concluído. Traz uma coca aê pra nóis.'.format(proximo))
else:
print('{} concluído. Faltam(m) {} atendimentos.'.format(proximo, len(self.atendimentos)))
'''teste do modelo de classes'''
l1 = Lavajato()
m1 = Motocicleta('Branca', 'ABC-1234')
c1 = CarroDePasseio('Preta', 'ABC-4321')
u1 = UtilitarioEsportivo('Prata', 'ABC-2134')
c2 = Caminhonete('Preta', 'ABC-3214')
c3 = Caminhao('Verde', 'ABC-3124')
l1.atender('Lavar', m1)
l1.atender('Lavar', c1)
l1.executar_proximo_servico()
l1.executar_proximo_servico()
l1.atender('Lavar e encerar', u1)
l1.atender('Lavar', c2)
l1.executar_proximo_servico()
l1.executar_proximo_servico()
l1.atender('Lavar completo', c3)
l1.executar_proximo_servico()
l1.executar_proximo_servico()
A saída da execução do código seria a seguinte:
Ow, tem cliente pra atender! Lavar: (Motocicleta, Branca, ABC-1234). Vai custar R$ 33.0, blz?
Ow, tem cliente pra atender! Lavar: (CarroDePasseio, Preta, ABC-4321). Vai custar R$ 30.0, blz?
Lavar: (Motocicleta, Branca, ABC-1234) concluído. Faltam(m) 1 atendimentos.
Lavar: (CarroDePasseio, Preta, ABC-4321) concluído. Traz uma coca aê pra nóis.
Ow, tem cliente pra atender! Lavar e encerar: (UtilitarioEsportivo, Prata, ABC-2134). Vai custar R$ 30.0, blz?
Ow, tem cliente pra atender! Lavar: (Caminhonete, Preta, ABC-3214). Vai custar R$ 30.0, blz?
Lavar e encerar: (UtilitarioEsportivo, Prata, ABC-2134) concluído. Faltam(m) 1 atendimentos.
Lavar: (Caminhonete, Preta, ABC-3214) concluído. Traz uma coca aê pra nóis.
Ow, tem cliente pra atender! Lavar completo: (Caminhao, Verde, ABC-3124). Vai custar R$ 30.0, blz?
Lavar completo: (Caminhao, Verde, ABC-3124) concluído. Traz uma coca aê pra nóis.
Já atendemos todo mundo. Só diboas...