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:

  1. Professor é subclasse de Pessoa
  2. Professor herda atributos e métodos de Pessoa (inclusive o construtor)
  3. o código Professor('José') cria uma instância de Professor, usando o construtor da superclasse Pessoa
  4. o código print(jose.nome) imprime o valor do atributo nome, presente na classe Professor por causa da herança (declarado explicitamente na classe Pessoa)

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 de Funcionario
  • FuncionarioDaDiretoria herda de Funcionario

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 e pessoa, nesta ordem. Além disso, esse construtor é responsável por criar dois atributos para a classe (matricula e pessoa)
  • o construtor da classe FuncionarioAdministrativo recebe três parâmetros: matricula, pessoa e cargo e também cria atributos com os mesmos nomes
  • o construtor da classe FuncionarioDaDiretoria recebe três parâmetros: matricula, pessoa e diretoria 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...

results matching ""

    No results matching ""