Raciocinar sobre se a herança (ou qualquer recurso único, na verdade) é necessário ou não, sem considerar também o restante da semântica da linguagem; você está discutindo no vácuo.
O que você precisa é de uma filosofia consistente de design de linguagem; o idioma precisa ser capaz de resolver com elegância os problemas para os quais foi projetado. O modelo para conseguir isso pode ou não exigir herança, mas é difícil julgá-lo sem uma visão geral.
Se, por exemplo, sua linguagem possui funções de primeira classe, aplicação de função parcial, tipos de dados polimórficos, variáveis de tipo e tipos genéricos, você cobriu praticamente as mesmas bases que faria com a herança clássica de POO, mas usando um paradigma diferente.
Se você possui ligação tardia, tipagem dinâmica, métodos como propriedades, argumentos de função flexíveis e funções de primeira classe, também cobre os mesmos motivos, mas novamente, usando um paradigma diferente.
(Encontrar exemplos para os dois paradigmas descritos é deixado como um exercício para o leitor.)
Então, pense no tipo de semântica que você deseja, brinque com eles e veja se eles são suficientes sem herança. Se não estiverem, você pode decidir incluir a herança na mistura ou pode decidir que algo está faltando.
Sim, é uma decisão de projeto perfeitamente razoável omitir herança.
De fato, existem boas razões para remover a herança de implementação, pois ela pode produzir código extremamente complexo e difícil de manter. Eu chegaria ao ponto de considerar a herança (como normalmente é implementada na maioria dos idiomas OOP) como uma característica incorreta.
O Clojure, por exemplo, não fornece herança de implementação, preferindo fornecer um conjunto de recursos ortogonais (protocolos, dados, funções, macros) que podem ser usados para obter os mesmos resultados, mas com muito mais clareza.
Aqui está um vídeo que eu achei muito esclarecedor sobre esse tópico geral, onde Rich Hickey identifica fontes fundamentais de complexidade em linguagens de programação (incluindo herança) e apresenta alternativas para cada uma: Simples facilitado
fonte
Quando me deparei com o fato de que as classes VB6 não suportam herança (apenas interfaces), isso realmente me incomodou (e ainda o faz).
No entanto, a razão de ser tão ruim é que ele também não tinha parâmetros de construtor; portanto, não era possível fazer injeção de dependência normal (DI). Se você tem DI, isso é mais importante que a herança, porque você pode seguir o princípio de favorecer a composição sobre a herança. Essa é a melhor maneira de reutilizar o código de qualquer maneira.
Não tendo Mixins embora? Se você deseja implementar uma interface e delegar todo o trabalho dessa interface em um conjunto de objetos por meio de injeção de dependência, então os Mixins são ideais. Caso contrário, você precisará escrever todo o código padrão para delegar todos os métodos e / ou propriedades ao objeto filho. Eu faço muito (graças ao C #) e é uma coisa que eu gostaria de não ter que fazer.
fonte
Para discordar de outra resposta: não, você lança recursos quando incompatíveis com algo que deseja mais. Java (e outras linguagens do GC) descartou o gerenciamento de memória explícita porque queria mais a segurança do tipo. Haskell eliminou a mutação porque queria mais raciocínio equacional e tipos extravagantes. Mesmo a C descartou (ou declarou ilegal) certos tipos de alias e outros comportamentos porque queria mais otimizações do compilador.
Portanto, a pergunta é: o que você quer mais do que herança?
fonte
free
/delete
dá a você a capacidade de invalidar referências; a menos que seu sistema de tipos seja capaz de rastrear todas as referências afetadas, isso torna o idioma inseguro. Em particular, nem C nem C ++ são do tipo seguro. É verdade que você pode fazer compromissos em um ou outro (por exemplo, tipos lineares ou restrições de alocação) para fazê-los concordar. Para ser mais preciso, eu deveria ter dito que o Java queria segurança de tipo com um sistema de tipo simples e particular e alocação irrestrita mais.null
referências). Proibirfree
é uma restrição adicional bastante arbitrária. Em resumo, se um idioma é do tipo seguro depende mais da sua definição de segurança do tipo do que do idioma.T
referência se refere anull
um objeto ou a uma classe que se estendeT
;null
é feio, mas as operaçõesnull
geram uma exceção bem definida, em vez de corromper o tipo invariante acima. Contraste com C ++: após uma chamada paradelete
, umT*
ponteiro pode apontar para a memória que não contém mais umT
objeto. Pior ainda, ao fazer uma atribuição de campo usando esse ponteiro em uma atribuição, você pode atualizar inteiramente um campo de um objeto de uma classe diferente se ele tiver sido colocado em um endereço próximo. Isso não é segurança de tipo por qualquer definição útil do termo.Não.
Se você deseja remover um recurso de idioma base, está declarando universalmente que nunca é necessário (ou é injustificadamente difícil de implementar, o que não se aplica aqui). E "nunca" é uma palavra forte em engenharia de software. Você precisaria de uma justificativa muito poderosa para fazer essa afirmação.
fonte
Falando de uma perspectiva estritamente C ++, a herança é útil para dois propósitos principais:
Para o ponto 1, desde que você tenha uma maneira de compartilhar código sem ter que fazer acrobacias demais, você pode acabar com a herança. Para o ponto 2. Você pode seguir o caminho do java e insistir em uma interface para implementar esse recurso.
Os benefícios de remover a herança são
A desvantagem é em grande parte entre "Não se repita" e Flexibilidade, por um lado, e prevenção de problemas, por outro. Pessoalmente, eu odiaria ver a herança passar do C ++ simplesmente porque algum outro desenvolvedor pode não ser inteligente o suficiente para prever os problemas.
fonte
Você pode fazer um trabalho muito útil sem herança de implementação ou mixins, mas gostaria de saber se você deve ter algum tipo de herança de interface, ou seja, uma declaração que diz que se um objeto implementa a interface A, ele também precisa da interface B (ou seja, A é uma especialização de B e, portanto, existe um relacionamento de tipo). Por outro lado, seu objeto resultante precisa apenas registrar que implementa as duas interfaces, portanto, não há muita complexidade lá. Tudo perfeitamente factível.
A falta de herança de implementação tem uma desvantagem clara: você não poderá construir vtables indexadas numéricas para suas classes e, portanto, terá que fazer pesquisas de hash para cada chamada de método (ou descobrir uma maneira inteligente de evitá-las). Isso pode ser doloroso se você estiver direcionando até valores fundamentais (por exemplo, números) através desse mecanismo. Mesmo uma implementação de hash muito boa pode ser cara quando você a atinge várias vezes em cada loop interno!
fonte