Herança múltipla
Herança múltipla é um recurso de algumas linguagens de programação de computador orientadas a objetos em que um objeto ou classe pode herdar recursos de mais de um objeto pai ou classe pai. É diferente da herança única, onde um objeto ou classe só pode herdar de um determinado objeto ou classe.
A herança múltipla tem sido um assunto controverso por muitos anos, com oponentes apontando para sua maior complexidade e ambigüidade em situações como o "problema do diamante", onde pode ser ambíguo quanto a qual classe pai um determinado O recurso é herdado se mais de uma classe pai implementa o referido recurso. Isso pode ser resolvido de várias maneiras, incluindo o uso de herança virtual. Métodos alternativos de composição de objetos não baseados em herança, como mixins e traits, também foram propostos para resolver a ambigüidade.
Detalhes
Na programação orientada a objetos (OOP), herança descreve um relacionamento entre duas classes em que uma classe (a classe filho) subclasses a classe pai. O filho herda métodos e atributos do pai, permitindo a funcionalidade compartilhada. Por exemplo, pode-se criar uma classe variável Mamífero com características como comer, reproduzir, etc.; em seguida, defina uma classe filha Gato que herda esses recursos sem ter que programá-los explicitamente, enquanto adiciona novos recursos como perseguir ratos.
A herança múltipla permite que os programadores usem mais de uma hierarquia totalmente ortogonal simultaneamente, como permitir que Gato herde de Personagem de desenho animado e Pet e Mamífero e acessar recursos de todas essas classes.
Implementações
As linguagens que suportam herança múltipla incluem: C++, Common Lisp (via Common Lisp Object System (CLOS)), EuLisp (via The EuLisp Object System TELOS), Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala (via use de classes mixin), OCaml, Perl, POP-11, Python, R, Raku e Tcl (integrado a partir do 8.6 ou via Incremental Tcl (Incr Tcl) em versões anteriores).
O tempo de execução do IBM System Object Model (SOM) suporta herança múltipla e qualquer linguagem de programação destinada ao SOM pode implementar novas classes SOM herdadas de várias bases.
Algumas linguagens orientadas a objetos, como Swift, Java, Fortran desde sua revisão de 2003, C# e Ruby implementam herança única, embora protocolos ou interfaces forneçam algumas das funcionalidades da verdadeira herança múltipla.
O PHP usa classes de características para herdar implementações de métodos específicos. Ruby usa módulos para herdar vários métodos.
O problema do diamante
O "problema do diamante" (às vezes referido como o "Diamante Mortal da Morte") é uma ambigüidade que surge quando duas classes B e C herdam de A, e a classe D herda de B e C. Se houver um método em A que B e C foram substituídos e D não o substitui, qual versão do método D herda: a de B ou a de C?
Por exemplo, no contexto de desenvolvimento de software GUI, uma classe Button
pode herdar de ambas as classes Rectangle
(para aparência) e Clicável
(para manipulação de funcionalidade/entrada) e as classes Rectangle
e Clicável
herdam da classe Object
. Agora, se o método equals
for chamado para um objeto Button
e não houver tal método na classe Button
, mas houver um substituído equals
método em Rectangle
ou Clicável
(ou ambos), qual método deve ser eventualmente chamado?
É chamado de "problema do diamante" devido ao formato do diagrama de herança de classe nessa situação. Nesse caso, a classe A está no topo, tanto B quanto C separadamente abaixo dela, e D une os dois na parte inferior para formar uma forma de diamante.
Mitigação
As linguagens têm maneiras diferentes de lidar com esses problemas de herança repetida.
- C# (desde C# 8.0) permite a implementação do método de interface padrão, causando uma classe
A
, interfaces de implementaçãoIa
eIb
com métodos semelhantes com implementações padrão, para ter dois métodos "herdados" com a mesma assinatura, causando o problema do diamante. É mitigado tanto por terA
para implementar o próprio método, portanto, remover a ambiguidade, ou forçar o chamador a lançar primeiro oA
objeto para a interface apropriada para usar sua implementação padrão desse método (por exemplo.((Ia) aInstance).Method();
). - C++ por padrão segue cada caminho de herança separadamente, então um
D
objeto realmente conter dois separadosA
objetos e usos deA
Os membros têm de ser devidamente qualificados. Se a herança deA
paraB
e a herança deA
paraC
são ambos marcados "virtual
" (por exemplo, "class B: virtual public A
"), C++ toma cuidado especial para apenas criar umA
objeto e usos deA
Os membros funcionam corretamente. Se a herança virtual e a herança não virtual são misturadas, há um único virtualA
, eA
para cada caminho de herança não virtualA
. C++ exige declarar explicitamente qual classe pai o recurso a ser usado é invocado a partir de i.e.Worker::Human.Age
. C++ não suporta herança repetida explícita, uma vez que não haveria nenhuma maneira de qualificar qual superclasse usar (ou seja, ter uma classe aparece mais de uma vez em uma única lista de derivação [class Dog: public Animal, Animal]). C++ também permite que uma única instância da classe múltipla seja criada através do mecanismo de herança virtual (i.e.Worker::Human
eMusician::Human
irá referenciar o mesmo objeto). - Common Lisp CLOS tenta fornecer comportamento padrão razoável e a capacidade de substituí-lo. Por padrão, para colocá-lo simplesmente, os métodos são classificados em
D,B,C,A
, quando B é escrito antes de C na definição de classe. O método com as classes de argumento mais específicas é escolhido (D>(B,C)>A); então na ordem em que as classes-mãe são nomeadas na definição de subclasse (B>C). No entanto, o programador pode substituir isso, dando uma ordem específica de resolução de métodos ou afirmando uma regra para combinar métodos. Isto é chamado combinação de método, que pode ser totalmente controlado. O MOP (protocolo metaobject) também fornece meios para modificar a herança, despacho dinâmico, instantâneo de classe e outros mecanismos internos sem afetar a estabilidade do sistema. - Curl permite apenas classes que são explicitamente marcadas como compartilhado ser herdado repetidamente. As classes compartilhadas devem definir uma construtor secundário para cada construtor regular na classe. O construtor regular é chamado pela primeira vez que o estado para a classe compartilhada é inicializado através de um construtor subclasse, e o construtor secundário será invocado para todas as outras subclasses.
- Em Eiffel, as características dos ancestrais são escolhidas explicitamente com diretivas de seleção e renomeação. Isso permite que as características da classe base sejam compartilhadas entre seus descendentes ou para dar a cada um deles uma cópia separada da classe base. Eiffel permite a união explícita ou separação de características herdadas de classes ancestrais. Eiffel vai automaticamente juntar características, se eles têm o mesmo nome e implementação. O escritor de classe tem a opção de renomear os recursos herdados para separá-los. A herança múltipla é uma ocorrência frequente no desenvolvimento Eiffel; a maioria das classes eficazes na biblioteca amplamente utilizada EiffelBase de estruturas de dados e algoritmos, por exemplo, têm dois ou mais pais.
- Ir evita o problema do diamante em tempo de compilação. Se uma estrutura
D
incorpora duas estruturasB
eC
que ambos têm um métodoF()
, satisfazendo assim uma interfaceA
, o compilador vai reclamar sobre um "seletor ambíguo" seD.F()
é chamado, ou se uma instância deD
é atribuído a uma variável de tipoA
.B
eC
Os métodos podem ser chamados explicitamente comD.B.F()
ouD.C.F()
. - O Java 8 introduz métodos padrão em interfaces. Se
A,B,C
são interfaces,B,C
pode cada um fornecer uma implementação diferente para um método abstrato deA
, causando o problema do diamante. Qualquer classeD
deve reexaminar o método (o corpo do qual pode simplesmente encaminhar a chamada para uma das super implementações), ou a ambiguidade será rejeitada como um erro de compilação. Antes do Java 8, Java não estava sujeito ao risco de problema Diamond, porque não suportava vários métodos padrão de herança e interface não estavam disponíveis. - JavaFX Script na versão 1.2 permite múltiplas heranças através do uso de mixinas. Em caso de conflito, o compilador proíbe o uso direto da variável ou função ambígua. Cada membro herdado ainda pode ser acessado por lançar o objeto para a mistura de interesse, por exemplo.
(individual as Person).printInfo();
. - Kotlin permite herança múltipla de Interfaces, no entanto, em um cenário de problema Diamond, a classe infantil deve substituir o método que causa o conflito de herança e especificar qual a implementação da classe pai deve ser usada. eg
super.someMethod()
- Logtalk suporta interface e implementação multi-herança, permitindo a declaração de método aliases que fornecem renomeação e acesso a métodos que seriam mascarados pelo mecanismo de resolução de conflitos padrão.
- Em OCaml, as classes-mãe são especificadas individualmente no corpo da definição de classe. Métodos (e atributos) são herdados na mesma ordem, com cada método recém- herdado substituindo quaisquer métodos existentes. OCaml escolhe a última definição de correspondência de uma lista de herança de classe para resolver qual implementação de método para usar sob ambiguidades. Para substituir o comportamento padrão, basta qualificar uma chamada de método com a definição de classe desejada.
- Perl usa a lista de classes para herdar como uma lista ordenada. O compilador usa o primeiro método que ele encontra por pesquisa profunda da lista de superclasses ou usando a linearização C3 da hierarquia de classes. Várias extensões fornecem esquemas alternativos de composição de classe. A ordem de herança afeta a semântica de classe. Na ambiguidade acima, classe
B
e seus ancestrais seriam verificados antes da classeC
e seus ancestrais, assim o método emA
seria herdado atravésB
. Isso é compartilhado com Io e Picolisp. Em Perl, esse comportamento pode ser substituído usando omro
ou outros módulos para usar a linearização C3 ou outros algoritmos. - Python tem a mesma estrutura que Perl, mas, ao contrário de Perl, inclui-a na sintaxe da linguagem. A ordem de herança afeta a semântica de classe. Python teve que lidar com isso após a introdução de novas classes de estilo, todas com um ancestral comum,
object
. Python cria uma lista de classes usando o algoritmo C3 (ou Method Resolution Order (MRO). Esse algoritmo aplica duas restrições: as crianças precedem seus pais e se uma classe herda de várias classes, elas são mantidas na ordem especificada no tupla de classes básicas (no entanto, neste caso, algumas classes altas no grafo de herança podem preceder as classes mais baixas no grafo). Assim, a ordem de resolução do método é:D
,B
,C
,A
. - Aulas de Ruby têm exatamente um pai, mas também podem herdar de vários módulos; definições de classe ruby são executadas, e a (re)definição de um método obscurece qualquer definição existente no momento da execução. Na ausência de metaprogramação em tempo de execução, isso tem aproximadamente a mesma semântica que a primeira resolução de profundidade mais correta.
- Scala permite múltiplas instantâneas características, que permite a herança múltipla, adicionando uma distinção entre a hierarquia de classe e a hierarquia de traços. Uma classe só pode herdar de uma única classe, mas pode misturar-se como muitos traços como desejado. Scala resolve os nomes dos métodos usando uma primeira busca de profundidade certa de 'traits' estendidos, antes de eliminar tudo, mas a última ocorrência de cada módulo na lista resultante. Assim, a ordem de resolução é: [
D
,C
,A
,B
,A
O que reduz para baixo para [D
,C
,B
,A
]. - Tcl permite várias classes parentais; a ordem de especificação na declaração de classe afeta a resolução de nome para membros usando o algoritmo de linearização C3.
Linguagens que permitem apenas herança única, onde uma classe só pode derivar de uma classe base, não têm o problema do diamante. A razão para isso é que tais linguagens têm no máximo uma implementação de qualquer método em qualquer nível na cadeia de herança, independentemente da repetição ou colocação de métodos. Normalmente, essas linguagens permitem que as classes implementem vários protocolos, chamados de interfaces em Java. Esses protocolos definem métodos, mas não fornecem implementações concretas. Essa estratégia tem sido usada por ActionScript, C#, D, Java, Nemerle, Object Pascal, Objective-C, Smalltalk, Swift e PHP. Todas essas linguagens permitem que as classes implementem vários protocolos.
Além disso, Ada, C#, Java, Object Pascal, Objective-C, Swift e PHP permitem herança múltipla de interfaces (chamadas de protocolos em Objective-C e Swift). As interfaces são como classes base abstratas que especificam assinaturas de método sem implementar nenhum comportamento. (Interfaces "Puras" como as do Java até a versão 7 não permitem nenhuma implementação ou dados de instância na interface.) No entanto, mesmo quando várias interfaces declaram a mesma assinatura de método, assim que esse método for implementado (definido) em qualquer lugar na cadeia de herança, ele substitui qualquer implementação desse método na cadeia acima dele (em suas superclasses). Portanto, em qualquer nível da cadeia de herança, pode haver no máximo uma implementação de qualquer método. Assim, a implementação do método de herança única não exibe o Problema do Diamante, mesmo com herança múltipla de interfaces. Com a introdução da implementação padrão para interfaces em Java 8 e C# 8, ainda é possível gerar um Diamond Problem, embora isso apareça apenas como um erro de tempo de compilação.
Contenido relacionado
Bill Joy
Quilobyte
HMAC