[Pesquisar este blog]

segunda-feira, 19 de março de 2018

POO::Plena-21-Sobreposição

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.

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
}

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.
A sobreposição de métodos é um mecanismo útil que permite fornecer uma nova implementação para um método existente. Isto possibilita tanto modificar (e especializar) a realização de uma operação em uma subclasse, quanto suprir uma primeira implementação no caso de métodos abstratos, assunto dos próximos posts.

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: