POO-P-20-Herança Simples | POO-P-22-Herança Múltipla |
A herança (inheritance) é um mecanismo da orientação a objetos que permite estabelecer relações de hierarquia entre classes e possibilita compartilhar membros selecionados entre elas. Esta é uma das características únicas e importantes da orientação a objetos [1][2][4][5][6][7].
A classe tomada como ponto de partida na herança torna-se a superclasse (ou classe-base ou classe-pai) de uma família, onde as subclasses (ou classes-derivadas ou classes-filha) criadas a partir desta primeira podem compartilhar de seus membros públicos e protegidos.
Além do compartilhamento de elementos existentes na superclasse, as subclasses podem adicionar novos atributos e operações próprios, de maneira a se tornarem extensões ou especializações de suas superclasses [4][5][6][7].
Mas também é possível substituir membros existentes, restringindo ou modificando as características herdadas. A substituição ou reescrita de métodos é o que se denomina sobreposição ou overriding.
A sobreposição de métodos
A substituição de métodos ou sobreposição de métodos é uma técnica conhecida também como method overriding ou apenas overriding. Constitui uma alternativa de implementação na qual uma subclasse recebe um método com a mesma assinatura de um método existente na superclasse, substituindo-o [6][7].
Esta técnica é tanto conveniente, como interessante, pois permite que a subclasse contenha uma implementação mais apropriada de um método definido na superclasse, o que mantém sua interface, facilita seu uso por meio do polimorfismo, ao mesmo tempo que torna possível alterar como tal operação é realizada [6][7].
Considere a classe Java que segue, denominada Motor.
public class Motor {
public static final double potenciaMax = 100;
public double getPotencia() {
return potenciaMax;
}
}
A classe Motor possui apenas um campo constante denominado potenciaMax, e um método de observação getPotencia().
Analise agora a classe MotorControlavel, que é uma subclasse de Motor.
public class MotorControlavel extends Motor {
private double taxa = 0;
public double getTaxa() { return taxa; }
public void setTaxa(double taxa) {
if (taxa<0 || taxa>1) {
throw new RuntimeException("Taxa invalida: " + taxa);
}
this.taxa = taxa;
}
@Override
public double getPotencia() {
return taxa * potenciaMax;
}
}
A classe MotorControlavel acrescenta um campo de tipo double denominado taxa e também os métodos de acesso getTaxa() e setTaxa(double) que permitem obter e ajustar o valor do campo taxa, garantindo que tenha um valor entre 0 e 1 (ou seja, 0 e 100%). O método getPotencia() da subclasse MotorControlavel é reescrito para fornecer uma implementação mais adequada, a qual substitui aquela existente na superclasse Motor, ou seja, efetua a sobreposição de método ou method override.
Objetos do tipo Motor e MotorControlavel podem ser instanciados e ter seus métodos getPotencia() acionados, o que provoca a execução da implementação efetivamente disponível em seu tipo, como segue:
Motor motor = new Motor();
MotorControlavel motorContr = new MotorControlavel();
motorContr.setTaxa(0.5);
// método getPotencia() acionado é da classe Motor
System.out.println("motor: " + motor.getPotencia());
// método getPotencia() acionado é da classe MotorControlavel
System.out.println("motorContr: " + motorContr.getPotencia());
Este pequeno exemplo ilustra como um método pode substituir outro existente em suas superclasses, provendo uma nova implementação que substitui o método para a classe onde ocorre a substituição e suas descendentes. Ao mesmo tempo, as várias classes da hierarquia oferecem a mesma interface, o que possibilita o uso polimórfico dos objetos destas classes, mas sempre acionando o método que corresponde ao tipo de origem (de instanciação) do objeto.
Motor mc = new MotorControlavel();
Apesar disso, quando é acionado o método getPotencia() por meio da referência do tipo Motor, a versão efetivamente invocada é aquela que pertence a classe MotorControlavel (do tipo de nascença), pois o objeto referenciado pela variável mc é, de fato, do tipo MotorControlavel, não sendo possível "burlar" a substituição efetuada no código por meio de referências de suas superclasses.
Assim, quando ocorre a sobreposição de um método, as instâncias das subclasses ficam impedidas de acionar o método original da superclasse, pois este é o objetivo deste mecanismo.
No entanto, o uso de super permite que, exclusivamente na implementação da subclasse, seja acessada a versão de um método definida na superclasse. O método getPotencia() de MotorControlavel poderia ser sido escrito como segue, aproveitando o código do método sobreposto.
public class MotorControlavel extends Motor {
private double taxa = 0;
// demais membros não modificados foram omitidos
:
public final double getPotencia() {
return taxa * super.getPotencia();
}
}
Embora não seja obrigatório, essa é uma prática habitual, visto que o método reescrito geralmente adiciona funcionalidade à sua versão sobreposta. Não há qualquer problema em não utilizar o método sobreposto quando não for conveniente.
Por exemplo, a classe MotorControlavel poderia sinalizar que o método getPotencia() não deve ser sobreposto em possíveis subclasses como segue:
public class MotorControlavel extends Motor {
private double taxa = 0;
// demais membros não modificados foram omitidos
:
@Override
public final double getPotencia() {
return taxa * potenciaMax;
}
}
Polimorfismo na sobreposição
Considerando a classe Motor e sua subclasse MotorControlavel, sabemos que é possível referenciar um objeto do tipo MotorControlavel por meio de uma referência do tipo Motor, pois um MotorControlavel "é um" Motor:Motor mc = new MotorControlavel();
Apesar disso, quando é acionado o método getPotencia() por meio da referência do tipo Motor, a versão efetivamente invocada é aquela que pertence a classe MotorControlavel (do tipo de nascença), pois o objeto referenciado pela variável mc é, de fato, do tipo MotorControlavel, não sendo possível "burlar" a substituição efetuada no código por meio de referências de suas superclasses.
Assim, quando ocorre a sobreposição de um método, as instâncias das subclasses ficam impedidas de acionar o método original da superclasse, pois este é o objetivo deste mecanismo.
No entanto, o uso de super permite que, exclusivamente na implementação da subclasse, seja acessada a versão de um método definida na superclasse. O método getPotencia() de MotorControlavel poderia ser sido escrito como segue, aproveitando o código do método sobreposto.
public class MotorControlavel extends Motor {
private double taxa = 0;
// demais membros não modificados foram omitidos
:
public final double getPotencia() {
return taxa * super.getPotencia();
}
}
Embora não seja obrigatório, essa é uma prática habitual, visto que o método reescrito geralmente adiciona funcionalidade à sua versão sobreposta. Não há qualquer problema em não utilizar o método sobreposto quando não for conveniente.
O modificador final
Nas situações onde se deseja impedir que um método seja sobrecarregado, pode ser empregado o modificador final, que indica que o método afetado não poderá ser substituído em qualquer subclasse.Por exemplo, a classe MotorControlavel poderia sinalizar que o método getPotencia() não deve ser sobreposto em possíveis subclasses como segue:
public class MotorControlavel extends Motor {
private double taxa = 0;
// demais membros não modificados foram omitidos
:
@Override
public final double getPotencia() {
return taxa * potenciaMax;
}
}
A anotação @Override
Existe uma anotação específica no Java, @Override, a qual indica que o método anotado (isto é, o método onde se aplica) sobrepõe outro existente em uma de suas superclasses. Quando a sobreposição é feita corretamente, esta anotação não produz qualquer efeito, servindo apenas como um lembrete no programa fonte. Mas caso o método anotado não substitua alguma operação, isto é, quando não ocorre a sobreposição, o compilador gera um erro indicando o problema.
Para aplicar a anotação, basta posicioná-la imediatamente antes da declaração do método anotado, com uma das formas que seguem:
@Override public int metodoAnotado() {
// código do método
}
}
@Override
public int metodoAnotado() {
// código do método
}
// código do método
}
Embora seja opcional, seu emprego constitui uma boa prática de programação, como feito na class MotorControlavel, pois funciona como um comentário para o programador e garante que sobreposição pretendida, de fato, ocorra.
Regras para sobreposição de métodos
O uso da sobreposição de métodos (method overriding) pode ser vantajoso em muitas situações, mas existem limitações (ou regras) que devem ser observadas em sua aplicação.
- Um método, em Java ou C#, só pode ser substituído numa subclasse, nunca na própria classe onde foi originalmente declarado.
- A lista de argumentos deve ser exatamente a mesma (quantidade, tipo e sequência dos parâmetros) do método sobreposto, caso contrário teremos uma mera sobrecarga de método.
- O tipo de retorno, embora não faça parte da assinatura, deve ser o mesmo daquele declarado no método original, sendo possível indicar uma subclasse do tipo de retorno.
- Construtores não são herdados, portanto não podem ser sobrepostos.
- Métodos declarados como final não podem ser sobrepostos.
- O nível de acesso (visibilidade) não pode ser mais restritivo que o método sobreposto, ou seja, a visibilidade deve ser a mesma ou maior. A maior implicação disso é que métodos públicos não podem ser sobrepostos por versões protegidas ou privadas.
- Métodos estáticos, de fato, não são sobrepostos, mas podem ser redeclarados.
- Métodos privados, não são herdados e, assim, não são sobrepostos, mas podem ser redeclarados.
- Os métodos sobrepostos podem lançar qualquer exceção não-monitoradas (unchecked exceptions), a despeito daquelas lançadas pelo método substituído.
- Os métodos sobrepostos não podem lançar outras exceções monitoradas (checked exceptions), além daquelas lançadas pelo método substituído ou de suas subclasses. É possível que o método sobreposto lance menos exceções.
POO-P-20-Herança Simples | POO-P-22-Herança Múltipla |
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.
Nenhum comentário:
Postar um comentário