Conformidade de tipo

terça-feira, 22 de setembro de 2009

Eu já falei sobre isso em algumas aulas mas sempre é válido ressaltar: herança é um acoplamento forte.

Acoplamentos fortes são indesejáveis, pois criam dependências pelas quais o impacto de uma mudança se propaga.
Uma herança entre duas classes traz várias implicações. Uma delas é dizer que a subclasse se comporta como a superclasse (Princípio de Substituição de Liskov*).

Assim, um pequeno polimorfismo de classe pode causar grandes transtornos se este relacionamento não for bem observado. Vou falar um pouco de covariância e contravariância.

Imaginem uma Superclasse e uma Subclasse, ambas com um método m declarado, sendo redefinido na subclasse. Este método tem um retorno e uma lista de parâmetros.

A covariância é a determinação de que o retorno da subclasse deve ser do mesmo tipo do retorno da superclasse ou de um tipo mais específico (subtipo) que este.

Então suponha a classe Object e a classe String herdando de Object.

O método m na Superclasse pode retornar Object. O método m redefinido na Subclasse pode retornar Object ou String (ou qualquer outro subtipo de Object).

Isto porque um código como

Object objeto = instancia.m();

deveria funcionar com

Superclasse instancia = new Superclasse();
ou
Superclasse instancia = new Subclasse();

Reparem que o código cliente (que usa as classes) assume que os retornos serão pelo menos do tipo declarado na Superclasse (ou mais específico) e, mais ainda, a variação deste retorno deve ser igual ou menor. Por exemplo, fosse o retorno um inteiro entre 0 e 10, a subclasse poderia retornar quaisquer números entre 0 e 10 (não necessariamente todos) mas nunca um número fora destes limites, pois o código cliente não pode estar preparado para lidar com situações que não estão previstas para a execução daquele método através de uma referência para a Superclasse.

Notem que para os objetos retornados, o ideal é que o espaço-estado* também seja igual ou menor.

Assim, conforme o tipo vai ficando mais específico (descendo na hierarquia), o retorno também vai ficando mais específico, configurando-se a covariância.

A contravariância é o mesmo conceito aplicado aos parâmetros. Ela é "contra" pois conforme se desce na hierarquia (ficando mais específico), os parâmetros vão aumentando os limites.

Se passo para o método m na superclasse um valor entre 0 e 5 e ele funciona, na subclasse, ele deve funcionar, no mínimo, com valores entre 0 e 5. Nenhum problema se ele funcionar também com 314.1592. O problema é se ele só funcionar com valores entre 0 e 3 ou não funcionar com 4 ou qualquer outra restrição. Devemos lembrar que a declaração
Superclasse instancia = new Subclasse();
é sempre válida e quem determina se uma classe funciona como deveria é o código cliente (novamente Princípio de Substituição de Liskov).

Vocês se preocupam com o funcionamento correto das heranças nas aplicações que fazem?

Abraço a todos!

*Prometo que no futuro falo um pouco do Princípio de Substituição de Liskov e de espaço-estado.

Bookmark and Share

Nenhum comentário:

Postar um comentário

 
addthis_config = { data_ga_tracker: pageTracker }