Herança múltipla - Multiple inheritance

A 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 características e recursos de mais de um objeto pai ou classe pai . É diferente da herança única, em que um objeto ou classe só pode herdar de um objeto ou classe particular.

A herança múltipla tem sido uma questão controversa por muitos anos, com os 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 parental uma característica particular é herdada se mais de uma a classe pai implementa o mesmo recurso. Isso pode ser tratado 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 lidar com a ambigüidade.

Detalhes

Na programação orientada a objetos (OOP), a herança descreve um relacionamento entre duas classes nas quais uma classe (a classe filha ) é uma subclasse da 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 Cat que herde 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 o Gato herde de um personagem de desenho animado e Animal de estimação e Mamífero e acesse 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 uso de classes mixin ), OCaml , Perl , POP-11 , Python , R , Raku e Tcl (integrado no 8.6 ou via Tcl incremental ( 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 direcionada 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.

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

Um diagrama de herança de classe de 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 substituíram , e D não o substitui, então 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 pode herdar de ambas as classes (para aparência) e (para manipulação de funcionalidade / entrada), e as classes e ambas herdam da classe. Agora, se o método é chamado para um objeto e não existe tal método na classe, mas existe um método sobrescrito em ou (ou ambos), qual método deve ser eventualmente chamado? ButtonRectangleClickableRectangleClickableObjectequalsButtonButtonequalsRectangleClickable

É chamado de "problema do diamante" devido ao formato do diagrama de herança de classes nessa situação. Nesse caso, a classe A está no topo, B e C separadamente abaixo dela, e D une os dois na parte inferior para formar uma forma de diamante.

Mitigação

As línguas têm maneiras diferentes de lidar com esses problemas de herança repetida.

  • C ++ por padrão segue cada caminho de herança separadamente, portanto, um Dobjeto conteria dois Aobjetos separados e os usos dos Amembros de devem ser qualificados adequadamente. Se a herança de Apara Be a herança de Apara Cestiverem marcadas com " virtual" (por exemplo, " class B : virtual public A"), C ++ terá um cuidado especial para criar apenas um Aobjeto e os usos dos Amembros de funcionarão corretamente. Se a herança virtual e a herança não virtual forem misturadas, haverá um único virtual Ae um não virtual Apara cada caminho de herança não virtual para A. C ++ requer declarar explicitamente de qual classe pai o recurso a ser usado é invocado ie Worker::Human.Age. C ++ não suporta herança repetida explícita, uma vez que não haveria maneira de qualificar qual superclasse usar (isto é, ter uma classe aparecendo mais de uma vez em uma única lista de derivação [classe Cachorro: Animal público, Animal]). C ++ também permite que uma única instância da classe múltipla seja criada por meio do mecanismo de herança virtual (ou seja, Worker::Humane Musician::Humanfará referência ao mesmo objeto).
  • Common Lisp CLOS tenta fornecer um comportamento padrão razoável e a capacidade de sobrescrevê-lo. Por padrão, para simplificar, 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); em seguida, na ordem em que as classes pai são nomeadas na definição da subclasse (B> C). No entanto, o programador pode sobrescrever isso, fornecendo uma ordem de resolução de método específica ou estabelecendo uma regra para a combinação de métodos. Isso é chamado de combinação de métodos, que pode ser totalmente controlada. O MOP ( protocolo de metaobjeto ) também fornece meios para modificar a herança, o despacho dinâmico , a instanciação da classe e outros mecanismos internos sem afetar a estabilidade do sistema.
  • Curl permite que apenas classes explicitamente marcadas como compartilhadas sejam herdadas repetidamente. As classes compartilhadas devem definir um construtor secundário para cada construtor regular na classe. O construtor regular é chamado na primeira vez que o estado da classe compartilhada é inicializado por meio de um construtor de subclasse, e o construtor secundário será chamado para todas as outras subclasses.
  • Em Eiffel , os recursos dos ancestrais são escolhidos explicitamente com as diretivas selecionar e renomear. Isso permite que os recursos da classe base sejam compartilhados entre seus descendentes ou dê a cada um deles uma cópia separada da classe base. Eiffel permite a junção ou separação explícita de recursos herdados de classes ancestrais. Eiffel irá juntar recursos automaticamente, se eles tiverem o mesmo nome e implementação. O escritor da classe tem a opção de renomear os recursos herdados para separá-los. A herança múltipla é uma ocorrência frequente no desenvolvimento de Eiffel; a maioria das classes eficazes na biblioteca EiffelBase amplamente usada de estruturas de dados e algoritmos, por exemplo, tem dois ou mais pais.
  • Go evita o problema do diamante em tempo de compilação. Se uma estrutura Dincorpora duas estruturas Be Cambas possuem um método F(), satisfazendo assim uma interface A, o compilador reclamará de um "seletor ambíguo" se D.F()for chamado, ou se uma instância de Dfor atribuída a uma variável do tipo A. Be Cmétodos 's pode ser chamado explicitamente com D.B.F()ou D.C.F().
  • Java 8 apresenta métodos padrão em interfaces. Se A,B,Cforem interfaces, B,Ccada uma pode fornecer uma implementação diferente para um método abstrato de Acausar o problema do diamante. Qualquer uma das classes Ddeve reimplementar o método (cujo corpo pode simplesmente encaminhar a chamada para uma das superimplementações) ou a ambigüidade será rejeitada como um erro de compilação. Antes do Java 8, o Java não estava sujeito ao risco de problema Diamond, porque não suportava herança múltipla e os métodos padrão de interface não estavam disponíveis.
  • O JavaFX Script na versão 1.2 permite herança múltipla por meio do uso de mixins . 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 lançando o objeto para o mixin de interesse, por exemplo (individual as Person).printInfo();.
  • O Kotlin permite a herança múltipla de interfaces, no entanto, em um cenário de problema Diamond, a classe filha deve substituir o método que causa o conflito de herança e especificar qual implementação da classe pai deve ser usada. por exemplo super<ChosenParentInterface>.someMethod()
  • Logtalk suporta interface e multi-herança de implementação, permitindo a declaração de apelidos de método que fornecem renomeação e acesso a métodos que seriam mascarados pelo mecanismo de resolução de conflito padrão.
  • No OCaml , as classes pai são especificadas individualmente no corpo da definição da classe. Métodos (e atributos) são herdados na mesma ordem, com cada método herdado recentemente substituindo qualquer método existente. OCaml escolhe a última definição correspondente de uma lista de herança de classe para resolver qual implementação de método usar sob ambigüidades. 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 encontra por pesquisa em profundidade 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 classes. A ordem de herança afeta a semântica da classe. Na ambigüidade acima, class Be seus ancestrais seriam verificados antes de class Ce seus ancestrais, portanto, o método em Aseria herdado por meio de B. Isso é compartilhado com Io e Picolisp . Em Perl, esse comportamento pode ser substituído usando o mroou outros módulos para usar a linearização C3 ou outros algoritmos.
  • Python tem a mesma estrutura do Perl, mas, ao contrário do Perl, inclui-a na sintaxe da linguagem. A ordem de herança afeta a semântica da classe. Python teve que lidar com isso após a introdução de classes de novo estilo, todas com um ancestral comum object,. Python cria uma lista de classes usando o algoritmo de linearização C3 (ou Method Resolution Order (MRO)). Esse algoritmo impõe duas restrições: os filhos precedem seus pais e se uma classe herda de várias classes, eles são mantidos na ordem especificada na tupla das classes base (no entanto, neste caso, algumas classes altas no gráfico de herança podem preceder as classes inferiores em o gráfico). Assim, a ordem de resolução método é: D, B, C, A.
  • As classes Ruby têm exatamente um pai, mas também podem herdar de vários módulos; As definições de classe ruby ​​são executadas, e a (re) definição de um método obscurece qualquer definição previamente 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 à direita.
  • Scala permite a instanciação múltipla de características , o que permite a herança múltipla ao adicionar uma distinção entre a hierarquia de classes e a hierarquia de características. Uma classe só pode herdar de uma única classe, mas pode misturar quantas características desejar. O Scala resolve nomes de métodos usando uma pesquisa de profundidade com a direita primeiro de 'características' estendidas, antes de eliminar todas as ocorrências, exceto a última, de cada módulo na lista resultante. Assim, a ordem de resolução é: [ D, C, A, B, A], o que reduz para baixo a [ D, C, B, A].
  • Tcl permite várias classes pai; a ordem de especificação na declaração de classe afeta a resolução de nomes para membros que usam o algoritmo de linearização C3 .

Linguagens que permitem apenas herança única , em que uma classe só pode derivar de uma classe base, não têm o problema do diamante. A razão para isso é que essas 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 localização dos 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 básicas abstratas que especificam assinaturas de método sem implementar nenhum comportamento. (Interfaces "puras" como as em Java até a versão 7 não permitem nenhuma implementação ou instância de dados na interface.) No entanto, mesmo quando várias interfaces declaram a mesma assinatura de método, assim que esse método é 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. Portanto, 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 Problema de Diamante, embora isso só apareça como um erro em tempo de compilação.

Veja também

Referências

Leitura adicional

links externos