Em muitos casos, as pessoas usam herança para fornecer uma característica a uma classe. Por exemplo, pense em um Pégaso. Com herança múltipla, você pode ficar tentado a dizer que o Pégaso estende Cavalo e Pássaro, porque você classificou o Pássaro como um animal com asas.
No entanto, os Birds têm outras características que os Pegasi não possuem. Por exemplo, os pássaros põem ovos, os pégasos têm um nascimento vivo. Se a herança é seu único meio de transmitir traços de compartilhamento, então não há como excluir o traço de postura de ovos do Pégaso.
Algumas linguagens optaram por tornar as características uma construção explícita dentro da linguagem. Outros o guiam suavemente nessa direção, removendo o MI do idioma. De qualquer forma, não consigo pensar em um único caso em que pensei "Cara, eu realmente preciso que o MI faça isso corretamente".
Também vamos discutir o que REALMENTE é a herança. Quando você herda de uma classe, você depende dessa classe, mas também tem que suportar os contratos que essa classe suporta, implícita e explícita.
Veja o exemplo clássico de um quadrado herdado de um retângulo. O retângulo expõe uma propriedade length e width e também um método getPerimeter e getArea. O quadrado substituiria comprimento e largura para que, quando um é definido, o outro seja definido para corresponder a getPerimeter e getArea funcionaria da mesma forma (2 * comprimento + 2 * largura para perímetro e comprimento * largura para área).
Há um único caso de teste que é interrompido se você substituir essa implementação de um quadrado por um retângulo.
var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea());
//Square returns 36 because setting the width clobbers the length
É difícil o suficiente para acertar as coisas com uma única cadeia de herança. Fica ainda pior quando você adiciona outro ao mix.
As armadilhas que mencionei com o Pegasus em MI e as relações Rectangle / Square são os resultados de um design inexperiente para as classes. Basicamente, evitar múltiplas heranças é uma maneira de ajudar os desenvolvedores iniciantes a evitarem atirar no próprio pé. Como todos os princípios de design, a disciplina e o treinamento baseados neles permitem que você descubra quando é aceitável romper com eles. Veja o Modelo Dreyfus de Aquisição de Habilidades , no nível Expert, seu conhecimento intrínseco transcende a confiança em máximas / princípios. Você pode "sentir" quando uma regra não se aplica.
E eu concordo que eu, de certa forma, trapaceiei com um exemplo do "mundo real" de por que o MI é desaprovado.
Vamos dar uma olhada em uma estrutura de interface do usuário. Especificamente, vamos dar uma olhada em alguns widgets que podem, à primeira vista, parecer que são simplesmente uma combinação de dois outros. Como uma caixa de combinação. Um ComboBox é um TextBox que tem um DropDownList de suporte. Ou seja Eu posso digitar um valor ou posso selecionar de uma lista pré-ordenada de valores. Uma abordagem ingênua seria herdar o ComboBox de TextBox e DropDownList.
Mas sua caixa de texto deriva seu valor do que o usuário digitou. Enquanto o DDL recebe seu valor do que o usuário seleciona. Quem leva precedente? A DDL pode ter sido projetada para verificar e rejeitar qualquer entrada que não estivesse em sua lista original de valores. Nós substituímos essa lógica? Isso significa que temos que expor a lógica interna para que os herdeiros sejam anulados. Ou pior, adicione lógica à classe base que está lá apenas para suportar uma subclasse (violando o Princípio da Inversão de Dependência ) .
Evitar MI ajuda você a evitar essa armadilha. E pode levar você a extrair traços comuns e reutilizáveis de seus widgets de interface do usuário para que possam ser aplicados conforme necessário. Um excelente exemplo disso é a Propriedade anexada do WPF que permite uma elemento de estrutura no WPF para fornecer uma propriedade que outro elemento de estrutura pode usar sem herdar do elemento de estrutura pai.
Por exemplo, um Grid é um painel de layout no WPF e possui propriedades anexadas Column e Row que especificam onde um elemento filho deve ser colocado na organização da grade. Sem propriedades anexadas, se eu quiser organizar um Button dentro de um Grid, o Button teria que derivar do Grid para que ele pudesse ter acesso às propriedades Column e Row.
Os desenvolvedores levaram esse conceito adiante e usaram as propriedades anexadas como forma de compor o comportamento (por exemplo, aqui está o meu post sobre como fazer um GridView classificável usando propriedades anexadas escritas antes do WPF incluir um DataGrid). A abordagem foi reconhecida como um padrão de design XAML chamado Comportamentos anexados .
Espero que isso forneça um pouco mais de detalhes sobre por que a herança múltipla geralmente é desaprovada.