Como as mixins ou características são melhores que a herança múltipla simples?

54

C ++ possui herança múltipla simples, muitos designs de linguagem a proíbem como perigosa. Mas algumas linguagens como Ruby e PHP usam sintaxe estranha para fazer a mesma coisa e chamam de mixins ou características. Ouvi muitas vezes que mixins / características são mais difíceis de abusar do que simples herança múltipla.

O que especificamente os torna menos perigosos? Existe algo que não é possível com mixins / características, mas possível com herança múltipla no estilo C ++? É possível encontrar o problema do diamante com eles?

Parece que usamos várias heranças, mas apenas desculpamos que sejam mixins / características para que possamos usá-las.

Gherman
fonte
7
Ansioso para alguém postar uma resposta bem escrita, atenciosa e completa para este.
Robert Harvey
7
Ruby e PHP não introduziram mixins e características. Mixins foram introduzidos em Flavors (1980), Traits foram introduzidos em Squeak Smalltalk (2001). Flavors foi a primeira linguagem orientada a objetos com herança múltipla e usou mixins. O C ++ só obteve herança múltipla na versão 2.0, lançada em 1989, 9 anos após o Flavors. Portanto, a pergunta deve ser: O Flavors possui mixins simples, mas algumas linguagens como o C ++ introduzem uma sintaxe estranha para fazer a mesma coisa e chamar de herança múltipla.
Jörg W Mittag

Respostas:

31

Existem vários problemas com herança múltipla quando usados ​​com classes de pleno direito, mas todos eles giram em torno da ambiguidade .

A ambiguidade aparece de algumas maneiras diferentes:

  1. Se você tem duas classes base com o mesmo campo xe o tipo derivado pede x, o que ele obtém?
    • Se as duas xvariáveis ​​tiverem tipos incongruentes, você poderá inferir.
    • Se eles são do mesmo tipo, você pode tentar mesclá-los na mesma variável.
    • Você sempre pode expô-los como nomes totalmente qualificados e estranhos.
  2. Se você tem duas classes base com a mesma função fcom assinaturas idênticas e alguém chama f, qual é chamado?
    • E se as duas classes base compartilharem outro ancestral virtual comum (o problema do diamante).
    • E se a função tiver assinaturas diferentes, mas compatíveis?
  3. Quando você constrói uma classe com duas classes base, qual construtor das classes base é chamado primeiro? Quando você destrói o objeto, o que é morto?
  4. Quando você coloca o objeto na memória, como o faz de maneira consistente?
  5. Como você lida com todos esses casos com 3 classes base? 10?

E isso ignora coisas como despacho dinâmico, inferência de tipo, correspondência de padrões e outras coisas que sei menos sobre as quais se tornam mais desafiadoras quando o idioma suporta herança múltipla de classes completas.

Traços ou mix-ins (ou interfaces ou ...) são todos os constructos que limitam especificamente os recursos de um tipo para que não haja ambiguidade. Eles raramente possuem algo. Isso permite que a composição dos tipos fique mais suave, porque não há duas variáveis ​​ou duas funções ... há uma variável e uma referência; uma função e uma assinatura. O compilador sabe o que fazer.

A outra abordagem comum adotada é forçar o usuário a "construir" (ou misturar) seu tipo, um de cada vez. Em vez de as classes base serem parceiros iguais no novo tipo, você adiciona um tipo a outro - substituindo tudo o que estava lá (geralmente com sintaxe opcional para renomear e / ou reexpor os bits substituídos).

Existe algo que não é possível com mixins / características, mas possível com herança múltipla no estilo C ++?

Dependendo do idioma - geralmente se torna problemático ou impossível mesclar implementações de funções e armazenamento para variáveis ​​de várias classes base e expô-las no tipo derivado.

É possível encontrar um problema de diamante com eles?

Ocasionalmente, variações menos graves aparecerão com base no seu idioma, mas geralmente não. O ponto principal das características é quebrar esse tipo de ambiguidade.

Telastyn
fonte
Você pode me dar um exemplo da linguagem / tecnologia que permite esses tipos de "construção" para uma instância?
Gherman
@german - Certamente não sou especialista em Scala, mas meu entendimento é que é isso que a withpalavra - chave faz lá.
Telastyn
"construções que limitam especificamente as capacidades de um tipo para que não haja ambiguidade": que tipo de limites? As interfaces não têm variáveis, então esse é um limite, mas as características do Scala e os módulos Ruby. Eles são limitados de outras maneiras?
David Moles
@ DavidMoles - essa é a maneira comum, também vi limitações nas regras de envio virtual (acho que as interfaces implementadas explicitamente em C #).
Telastyn 17/07/2015