POO-P-21-Sobreposição | POO-P-23-Polimorfismo |
A herança (inheritance) é uma das formas mais importantes do polimorfismo que possibilita a especialização de classes. De fato, é uma técnica que possibilita compartilhar partes da implementação de uma classe com outra, além de permitir a adição de novos elementos, o que torna as novas classes em versões especializadas.
Com isto podem ser criadas famílias de classes, cujos participantes possuem características semelhantes, o que facilita seu desenvolvimento, sua manutenção e também seu reuso. Este mecanismo é único, pois só existe na orientação a objetos.
Considerando que existem muitos tipos de objetos e de entidades, reais e abstratas, estes podem ser classificados em hierarquias, nas quais os elementos superiores agrupam as características comuns dos seus derivados. Esta técnica é usada frequentemente nos diversos campos da conhecimento para criar classificações (ou taxonomias) de seres, componentes, máquinas ou funções.
Desta maneira, a herança é um mecanismo da orientação a objetos que permite representar as relações de semelhança, identificadas afirmativamente pela questão 'é um(a)?', existentes entre conceitos. Uma de suas contribuições diretas é evitar que atributos e operações comuns sejam definidos repetidamente em classes relacionadas.
Existem duas formas de herança: a herança simples (single inheritance), onde uma única classe é tomada como base na criação de uma subclasse; e a herança múltipla (multiple inheritance), na qual duas ou mais classes são, simultaneamente, tomadas como base para definição de novas classes derivadas.
A linguagem de programação Java foi originalmente projetada para oferecer apenas a herança simples, assim como o C#, apesar de um pouco mais recente.
Herança múltipla
Em algumas situações, as relações existentes entre conceitos não podem ser representadas pela herança simples, que relaciona diretamente uma única superclasse (ou classe base) com sua subclasse (ou classe derivada). Nestes casos, torna-se necessário representar as subclasses relacionadas a partir de duas ou mais outras classes. Esta é a herança múltipla, que permite que uma nova classe compartilhe elementos de duas ou mais superclasses.
Como as linguagens de programação Java e C# não possuem herança múltipla, os exemplos desta seção utilizarão a sintaxe da linguagem C++.
A sintaxe para a construção de classes com herança múltipla em C# é a seguinte:
[acesso] class <NomeSubClasse> : [acesso] <NomeSuperClasse1>,
[acesso] <NomeSuperClasse2>,
... ,
[acesso] <NomeSuperClasseN> {
// corpo da subclasse
};
// implementação de membros da subclasse
Pode ser observado que, após o nome da subclasse o sinal de dois pontos (':'), segue uma relação dos nomes das superclasses tomadas para herança, separados por vírgulas (',').
A herança múltipla possibilita que uma nova classe seja criada pela combinação de características de várias classes existentes, evitando a repetição destas características no código e permitindo tratamento polimórfico (como será visto no post sobre polimorfismo).
A classe Funcionario, que segue representa um funcionário qualquer de uma empresa que pode ser distinguido dos demais por meio do atributo privado long id que tem papel de um identificador.
class Funcionario {
// membros privados
long id;
// membros publicos
public:
Funcionario(long nid) {
cout << "Construtor Funcionario: " << nid << endl;
id = nid;
}
~Funcionario() { cout << "Destrutor Funcionario\n"; }
long getId() { return id; }
};
Observe que o único construtor disponível em Funcionario exige um parâmetro de tipo long que indica o id do funcionário.
Já a classe Temporario representa os colaboradores contratados por período determinado, sendo seu principal atributo o privado de tipo int periodo.
class Temporario {
// membros privados
int periodo;
// membros publicos
public:
Temporario(int p) {
cout << "Construtor Temporario: " << p << endl;
periodo = p;
}
~Temporario() { cout << "Destrutor Temporario\n"; }
int getPeriodo() { return periodo; }
};
Aqui também temos que o único construtor disponível em Temporario exige um parâmetro de tipo int que requer o número de períodos do contrato de trabalho temporário.
Uma empresa pode possuir engenheiros, que são um tipo particular de funcionário, ou seja, cuja classe Engenheiro pode ser uma subclasse de Funcionario, empregando herança simples. Observe a implementação da classe Engenheiro que segue.
class Engenheiro: public Funcionario {
// membros publicos
public:
Engenheiro(long nid): Funcionario(nid) {
cout << "Construtor Engenheiro: " << nid << endl;
}
~Engenheiro() { cout << "Destrutor Engenheiro\n"; }
};
Note que a classe Engenheiro não possui campos privados. Além disso, o construtor declarado aciona o construtor da superclasse Funcionario para prover o valor requerido do id.
Como a classe Engenheiro toma, por meio da herança simples e pública, a superclasse Funcionario, compartilha seus membros públicos, isto é, a operação getId().
Uma empresa também pode possuir estagiários, que são funcionários temporários, de modo que uma classe Estagiario, como segue, deve utilizar da herança múltipla para compartilhar aspectos das classes Funcionario (o id do funcionário) e também Temporario (o número de períodos de contrato de trabalho).
class Estagiario: public Funcionario, public Temporario {
public:
Estagiario(long nid, int p): Funcionario(nid), Temporario(p) {
cout << "Construtor Estagiario: " << nid << "," << p << endl;
}
~Estagiario() { cout << "Destrutor Estagiario\n"; }
};
Outra vez é possível observar que o construtor declarado aciona os construtores das superclasses Funcionario (para prover o valor requerido do id) e Temporario (para suprir o valor dos períodos).
A classe Estagiario toma, por meio da herança múltipla e pública, as superclasses Funcionario e Temporario; compartilhando seus membros públicos, isto é, as operações getId() (de Funcionario) e getPeriodo() (de Temporario).
A figura que segue ilustra um diagrama de classe UML simplificado que mostra as relações de herança existentes entre as classes Funcionario, Temporario, Engenheiro e Estagiario.
A herança múltipla é um recurso sofisticado que deve ser utilizado com bastante cautela principalmente por programadores menos experientes. Alternativas tais como a herança simples, a agregação ou composição (que foram tratadas em posts anteriores) não devem ser descartadas e podem ser mais simples de implementar e utilizar.
A herança múltipla é um aspecto complexo de C++, muito debatido. Para alguns é uma característica desnecessária, que pode ser substituída por outros mecanismos mais simples, evitando os diversos problemas de ambigüidade decorrentes. Para outros trata questões específicas e quando bem empregada conduz a resultados satisfatórios.
Por conta deste debate, tanto Java como C# não oferecem a herança múltipla, tendo seus projetistas optado por oferecer apenas a herança simples, complementada pelo mecanismo de implementação de interfaces, que será discutido num post mais a frente.
POO-P-21-Sobreposição | POO-P-23-Polimorfismo |
Referências Bibliográficas
[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.
[12] GAMMA, E.; HELM, R.; JOHNSON, R.; VLISSIDES, J.. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.