// java
public interface Container {
public int size();
public default boolean isEmpty() {
return size() == 0;
}
}
No paradigma de Programação Orientada a Objetos, Herança é uma capacidade que permite que novos tipos possam tirar proveito da estrutura de tipos já existentes. Estes novos tipos são chamadas tipos derivadas e a tipo preexistente é conhecido como tipo base [[1]]. O conjunto dos tipos base e seus tipos derivados, forma a Hierarquia de Herança.
Os tipos derivadoss têm acesso privilegiado aos membros do tipo preexistente, tomando-os para si como se fossem seus. Dizemos que o tipo derivado herda os membros do tipo base. Herdar significa apenas ter acesso. Outros tipos, que não fazem parte da hierarquia de herança não sabem deste acesso privilegiado e não sabem diferenciar os membros da classe base daqueles que existem apenas da classe derivada.
O principal objetivo da Herança é possibilitar o uso de Categorização [[2]]. Isso permite agrupar objetos (instâncias das classes) em categorias. O agrupamento, por sua vez, permite organizar melhor o código e o modelo abstrato por detrás do código. Além disso, permite construir algoritmos mais genéricos baseados nos membros que uma categoria de objetos tem, ao invés dos membros que cada elemento da categoria tem.
A categorização é uma das capacidades mais inerentes ao Ser Humano e à forma como ele pensa. A herança nos permite transportar para o modelo de objetos ( e posteriormente para o código) essa forma de pensar. Isso torna o programa muito mais próximo de como um humano pensa e mais longe de como uma máquina reage, facilitando a construção de programas poderosos com pouco esforço.
É comum afirmar que Herança possibilita a reutilização de código. Isso é verdade, mas esse é apenas um efeito secundário do poder da categorização que a herança introduz nos modelos orientados a objetos (O.O.). Esse não é o objetivo principal do uso da Herança.
Linguagens Orientadas as Objetos contém normalmente duas especies de tipos: classes e interfaces. Classes são tipos normais cujos membros podem ser métodos ou attributos. Classes contém comportamentos. Interfaces representam apenas contrados de métodos. Não contém comportamentos mas contratos de como os comportamentos devem se realizar.
A Herança nos dá uma forma de categorizar as classes e por consequência as suas instâncias. A principal consequência prática disso é nos encontrarmos muitas vezes com a necessidade de manipular instâncias definidas apenas através de uma categoria.
O recurso de Polimorfismo – em particular o de variáveis polimórficas – permite manipular todas possíveis instâncias de objetos de uma certa categoria de forma simples. Às vezes o recurso de Polimorfismo é confundido com o de Herança, em particular em linguagens Orientadas as Objetos. Eles se completam, é verdade, mas são conceitos distintos, com propósitos distintos ,já que o polimorfismo permite manipular diferentes formas como se fossem uma, enquanto herança permite definir essas formas.
Existem diferentes tipo de relações de herança dependendo dos tipos intervenientes.
Quando uma classe herda de outra classe, ou uma interface de outra interface. Dizemos que a classe extende a outra classe. Isto significa que todos os métodos que a classe base disponibilizou para a classe derivada podem ser usados por esta.
A extensão implica a relação É-UM. A classe X só deve extender Y se todo o X é um Y.
Quando uma classe herda de uma interface dizemos que a classe implementa a interface. Isto significa que a classe tem o mesmo contrato que aquele ditado pela interface. Cabe à classe incluir o código que permite que ele seja consistente com a intensão da interface.
A implementação implica a relação SE-COMPORTA-COMO-UM. A classe X só deve implementar Y se todo o X se comportar como um Y.
Herança simples significa que cada tipo deriva apenas de um só tipo base. Normalmente, mas nem sempre, as classes são restritas para terem apenas herança simples em relação a outras classes. Ou seja, certas linguagens permitem apenas Extensão Simples, como é o caso de Java e C#.
Herança múltipla significa que cada tipo pode derivar de mais do que um único tipo base. Certas linguagens como C++ permitem herança múltipla entre classes e interfaces. Linguagens mais modernas permitem apenas Implementação Múltipla - herança múltipla entre interfaces
Atualmente, nas linguagens modernas, como Java, C#, Scala ou Kotlin, o modelo é o seguinte:
Herança |
Simples |
Múltipla |
Extensão |
Classes apenas podem herdar de 0 ou 1 classe |
Interfaces podem extender qualquer número de outras interfaces |
Implementação |
Classes apenas podem implementar 0 ou 1 interface |
Classe podem implementar qualquer número de outras interfaces |
A razão de porque é seguro que multiplas interfaces sejam implementadas pela mesma classe, ou extentidas por outras interface é que não ha comportamento associado, ou seja, não ha código associado aos métodos. No caso em que ha código associado é possivel cairmos no problema do diamante [[3]].
Por outro lado, se a extensão implica a relação É-UM não faz sentido que algo seja mais do que uma coisa simultâneamente.
Trait é um tipo especial de interface que pode conter código. Este código só pode se referir a métodos que a interface declara. Por exemplo, podemos construir um trait simples:
// java
public interface Container {
public int size();
public default boolean isEmpty() {
return size() == 0;
}
}
Repare como o método isEmpty
é construido com base no método size
. A classe que implementar estas interface terá apenas que implementar o método size
e nada mais. Ela pode, se precisar, implementar o método isEmpty
também, mas não é obrigatório.
Por que traits têm código, quando uma classe implementa mais do que um trait, podemos cair no referido Problema do Diamante[[3]]. Cada linguagens que permite traits tem uma forma diferente de lidar com isto.
A Extensão Simples, torna a herança por extensão recurso escasso. Você só a pode extender uma vez, uma única classe, então é bom pensar bem qual será essa classe. Extensão é um recurso muito poderoso na Orientação a Objetos e como tal deve ser restricto. Não apenas porque iriamos cair em problemas como o Problema do Diamante[[3]], mas porque do ponto de vista lógico um tipo só pode ser uma coisa de cada vez (É-UM). Se ele tem que pertencer a mais do que uma categoria, então essas categorias tem que ser comportamental (SE-COMPORTA-COMO-UM) e a herança por implementação é o que queremos.
Aqui vale a máxima de que se um recurso é escasso ele é poderoso e se é poderoso deve ser escasso.
Herança é um recurso poderoso e perigoso. Você só pode usar uma vez, e se usar errado, não há retorno. É comum ficar com a ideia que a herança é um mero artifício programático que permite agrupar coisas comuns em uma classe "acima". Herança causa esse efeito, mas não é para isso que ela serve. Ela serve para categorizar os objetos. Usar a herança pelos motivos errados pode atrapalhar o seu modelo sem que você se aperceba. Você pode estar categorizando o objeto de uma forma que não útil. Isso é muito fácil de fazer e resistir a essa tentação implica uma concentração e uma preocupação com o detalhe.
Alguns autores chegam ao ponto de propor que o mecanismo de Herança é um erro. Que não deveria ser dada essa possibilidade aos programadores. Essa afirmação é feita com a mesma força que se condena hoje o uso da instrução goto
de outras linguagens. Isso é um pouco extremo. Não podemos culpar as ferramentas pelo uso que é feito delas, apenas temos que chamar a atenção para o seu uso perigoso. Martelos, chaves-de-fendas ou moto-serras podem ser usadas para causar dano, mas não é por isso que vamos deixar de as usar para a finalidade para que foram criadas. O mesmo acontece com herança.
No caso do goto
ele foi substituido por práticas melhores como sejam as diretivas de decisão (if
e switch
) e de repetição (for
e while
). As linguagens evoluiram para promover melhores práticas sem ter que usar goto
. No caso da herança ainda não ha algo que a substitua, então embora muita gente advogue a composição em vez de herança isto não significa que é sempre possivel. Tem casos onde não é, e é ai que precisamos de herança.
Use o recurso de herança de forma responsável. Ele é escasso e poderoso o suficiente para ser manipulado com atenção.
[1] Inheritance, Wikipédia (http://en.wikipedia.org/wiki/Inheritance_(computer_science))
[2] Categorization, Wikipédia (http://en.wikipedia.org/wiki/Categorization)
[3] Multiple Inheritance, Wikipédia (https://en.wikipedia.org/wiki/Multiple_inheritance)