De onde vem esse conceito de “favorecer a composição sobre a herança”?


143

Nos últimos meses, o mantra "favorece a composição sobre a herança" parece ter surgido do nada e se tornou quase algum tipo de meme na comunidade de programação. E toda vez que vejo, fico um pouco confuso. É como se alguém dissesse "brocas favoritas sobre martelos". Na minha experiência, composição e herança são duas ferramentas diferentes com diferentes casos de uso, e tratá-las como se fossem intercambiáveis ​​e uma fosse inerentemente superior à outra não faz sentido.

Além disso, nunca vejo uma explicação real sobre por que a herança é ruim e a composição é boa, o que me deixa mais desconfiada. Deveria ser aceito apenas com fé? A substituição e o polimorfismo de Liskov têm benefícios bem conhecidos e bem definidos, e a IMO compreende todo o ponto de usar a programação orientada a objetos, e ninguém nunca explica por que eles devem ser descartados em favor da composição.

Alguém sabe de onde vem esse conceito e qual é a lógica por trás dele?


45
Como outros já apontaram, já faz muito tempo - estou surpreso que você esteja ouvindo isso agora. É intuitivo para quem constrói grandes sistemas em linguagens como Java há bastante tempo. É essencial para qualquer entrevista que eu der e, quando um candidato começa a falar sobre herança, começo a duvidar do nível de habilidade e da quantidade de experiência. Aqui está uma boa introdução às razões pelas quais a herança é uma solução frágil (há muitos outros): artima.com/lejava/articles/designprinciples4.html
Janx

6
@Janx: Talvez seja isso. Não construo grandes sistemas em linguagens como Java; Eu os construo em Delphi, e sem a substituição e o polimorfismo de Liskov, nunca conseguiríamos fazer nada. Seu modelo de objeto é diferente em certos aspectos do Java ou C ++, e muitos dos problemas que essa máxima parece ter como objetivo resolver não existem realmente, ou são muito menos problemáticos, no Delphi. Diferentes perspectivas sob diferentes pontos de vista, eu acho.
Mason Wheeler

14
Passei vários anos em uma equipe construindo sistemas relativamente grandes em Delphi e as altas árvores de herança certamente morderam nossa equipe e nos causaram dores significativas. Suspeito que sua atenção aos princípios do SOLID esteja ajudando você a evitar as áreas problemáticas, não o uso do Delphi.
Bevan

8
Últimos meses?!?
Jas

6
IMHO esse conceito nunca foi totalmente ajustado à variedade de linguagens que suportam herança de interface (isto é, subtipagem com interfaces puras) e herança de implementação. Muitas pessoas seguem esse mantra e não usam interfaces suficientes.
Uri

Respostas:


136

Embora eu ache que já ouvi discussões sobre composição versus herança muito antes do GoF, não consigo identificar uma fonte específica. Poderia ter sido Booch de qualquer maneira.

<rant>

Ah, mas como muitos mantras, este degenerou de acordo com linhas típicas:

  1. é introduzido com uma explicação e argumento detalhados por uma fonte respeitada que cunha o slogan como um lembrete da complexa discussão original
  2. Ele é compartilhado com uma piscadela de parte do clube por alguns in-the-know por um tempo, geralmente ao comentar sobre erros de n00b
  3. logo é repetido sem pensar por milhares e milhares que nunca leem a explicação, mas adoram usá-la como uma desculpa para não pensar e como uma maneira barata e fácil de se sentir superior aos outros.
  4. Eventualmente, nenhuma quantidade razoável de desmistificação pode conter a maré do "meme" - e o paradigma degenera em religião e dogma.

O meme, originalmente destinado a levar n00bs à iluminação, agora é usado como um clube para espancá-los inconscientes.

Composição e herança são coisas muito diferentes e não devem ser confundidas uma com a outra. Embora seja verdade que a composição possa ser usada para simular herança com muito trabalho extra , isso não torna a herança um cidadão de segunda classe, nem torna a composição o filho favorito. O fato de muitos n00bs tentarem usar a herança como atalho não invalida o mecanismo, e quase todos os n00bs aprendem com o erro e, assim, melhoram.

Por favor PENSAR sobre seus projetos, e parar de jorrar slogans.

</rant>


16
Booch argumenta que a herança de implementação introduz um alto acoplamento entre as classes. Por outro lado, ele considera herança o conceito-chave que distingue OOP da programação procedural.
Nemanja Trifunovic

13
Deve haver uma palavra para esse tipo de coisa. Regurgitação prematura , talvez?
Jörg W Mittag

8
@ Jörg: Você sabe o que aconteceu com um termo como Regurgitação Prematura? Exatamente o que é explicado acima. :) (. Btw quando é regurgitação já não prematura?)
Bjarke Freund-Hansen

6
@ Nemanja: Certo. A questão é se esse acoplamento é realmente ruim. Se as classes estiverem conceitualmente fortemente acopladas e conceitualmente formarem um relacionamento supertipo-subtipo, e nunca puderem ser dissociadas conceitualmente, mesmo se formalmente dissociadas no nível da linguagem, então o acoplamento forte é bom.
dsimcha

5
O mantra é simples: "só porque você pode moldar o play-doh para parecer com qualquer coisa não significa que você deve fazer tudo com play-doh". ;)
Evan Plaice

73

Experiência.

Como você diz, elas são ferramentas para trabalhos diferentes, mas a frase surgiu porque as pessoas não a estavam usando dessa maneira.

A herança é primariamente uma ferramenta polimórfica, mas algumas pessoas, muito mais tarde, tentam usá-la como uma maneira de reutilizar / compartilhar código. A lógica é "bem, se eu herdar, recebo todos os métodos de graça", mas ignorando o fato de que essas duas classes potencialmente não têm relação polimórfica.

Então, por que favorecer a composição sobre a herança - bem, simplesmente porque, na maioria das vezes, o relacionamento entre classes não é polimórfico. Ele existe simplesmente para ajudar a lembrar as pessoas a não reagirem por herança.


7
Então, basicamente, você não pode dizer "você não deveria estar usando OOP em primeiro lugar, se não entender a substituição de Liskov"; então, você diz "favorece a composição sobre a herança" em vez disso, como uma tentativa de limitar o dano causado por incompetentes codificadores?
Mason Wheeler

13
@Mason: Como qualquer mantra, "favorecer a composição sobre a herança" é direcionado a programadores iniciantes. Se você já sabe quando usar herança e quando usar composição, é inútil repetir um mantra como esse.
Dean Harding

7
@ Dean - Não tenho certeza se iniciante é o mesmo que não entende as melhores práticas de herança. Eu acho que há mais do que isso. A má herança é a causa de muitas, muitas dores de cabeça no meu trabalho e não é um código escrito por programadores que seriam considerados "iniciantes".
Nicole

6
@ Renesis: A primeira coisa que penso quando ouço isso é: "algumas pessoas têm dez anos de experiência e outras têm um ano de experiência repetido dez vezes".
Mason Wheeler

8
Eu estava projetando um projeto bastante grande logo após o primeiro "Getting" OO e confiei muito nele. Embora funcionasse bem, eu constantemente achava o design frágil - causando pequenas irritações aqui e ali e às vezes tornando certos refatores uma vadia completa. É meio difícil explicar toda a experiência, mas a frase "Favorece a concorrência sobre a herança" a descreve EXATAMENTE. Observe que ele não diz ou mesmo implica "Evitar a Herança"; simplesmente dá uma pequena cutucada quando a escolha não é óbvia.
Bill K

43

Não é uma idéia nova, acredito que foi realmente introduzida no livro de padrões de design do GoF, publicado em 1994.

O principal problema da herança é que é uma caixa branca. Por definição, você precisa conhecer os detalhes de implementação da classe da qual está herdando. Com a composição, por outro lado, você se preocupa apenas com a interface pública da classe que está compondo.

Do livro GoF:

A herança expõe uma subclasse a detalhes da implementação de seus pais, é comum dizer que 'a herança quebra o encapsulamento'

O artigo da wikipedia no livro do GoF tem uma introdução decente.


14
Eu não concordo com isso. Você não precisa conhecer os detalhes de implementação da classe da qual está herdando; somente os membros públicos e protegidos expostos pela classe. Se você precisar conhecer os detalhes da implementação, você ou quem escreveu a classe base está fazendo algo errado e, se a falha estiver na classe base, a composição não ajudará a corrigi-la.
Mason Wheeler

18
Como você pode discordar de algo que não leu? Há uma página sólida e meia de discussão do GoF que você está obtendo apenas uma pequena visão.
philosodad

7
@pholosodad: Não estou discordando de algo que não li. Estou discordando do que Dean escreveu: "Por definição, você precisa conhecer os detalhes de implementação da classe da qual está herdando", que li.
Mason Wheeler

10
O que escrevi é apenas um resumo do que está descrito no livro do GoF. Talvez eu tenha escrito um pouco mais (você não precisa conhecer todos os detalhes da implementação), mas essa é a razão geral pela qual o GoF diz favorecer a composição sobre a herança.
Dean Harding

2
Corrija-me se estiver errado, mas vejo como "favorecer relações explícitas de classe (por exemplo, interfaces) em vez de implícitas (por exemplo, herança)". O primeiro diz o que você precisa, sem dizer como fazê-lo. Este último não apenas lhe diz como fazê-lo, mas também o fará se arrepender no futuro.
quer

26

Para responder a parte de sua pergunta, acredito que esse conceito apareceu pela primeira vez no livro GOF Design Patterns: Elements of Reusable Oriented Object Software , publicado pela primeira vez em 1994. A frase aparece na parte superior da página 20, na introdução:

Favorecer a composição do objeto sobre a herança

Eles precedem esta afirmação com uma breve comparação entre herança e composição. Eles não dizem "nunca use herança".


23

"Composição sobre herança" é uma maneira curta (e aparentemente enganosa) de dizer "Ao sentir que os dados (ou comportamento) de uma classe devem ser incorporados a outra classe, sempre considere usar a composição antes de aplicar cegamente a herança".

Por que isso é verdade? Porque a herança cria um acoplamento apertado em tempo de compilação entre as 2 classes. A composição, ao contrário, é um acoplamento frouxo, o que, entre outros, permite uma separação clara de preocupações, a possibilidade de alternar dependências em tempo de execução e uma testabilidade de dependências mais fácil e mais isolada.

Isso significa apenas que a herança deve ser tratada com cuidado, pois tem um custo, não que não seja útil. Na verdade, "Composição sobre herança" geralmente acaba sendo "Composição + herança sobre herança", pois muitas vezes você deseja que sua dependência composta seja uma superclasse abstrata e não a própria subclasse concreta. Permite alternar entre diferentes implementações concretas de sua dependência em tempo de execução.

Por esse motivo (entre outros), você provavelmente verá a herança usada com mais frequência na forma de implementação de interface ou classes abstratas do que a herança de baunilha.

Um exemplo (metafórico) poderia ser:

"Eu tenho uma classe Snake e quero incluir como parte dessa classe o que acontece quando a Snake morde. Eu ficaria tentado a fazer com que a Snake herde uma classe BiterAnimal que possui o método Bite () e substitua esse método para refletir a mordida venenosa. Mas o Composition over Inheritance me avisa que eu deveria tentar usar a composição ... No meu caso, isso pode se traduzir no fato de o Snake ter um membro Bite.A classe Bite pode ser abstrata (ou uma interface) com várias subclasses. coisas boas como ter as subclasses VenomousBite e DryBite e ser capaz de mudar a mordida na mesma instância Snake à medida que a cobra envelhece.Além disso, lidar com todos os efeitos de uma mordida em sua própria classe separada pode permitir que eu a reutilize nessa classe Frost , porque a geada morde, mas não é um BiterAnimal, e assim por diante ... "


4
Muito bom exemplo com a cobra. Eu conheci um caso semelhante recentemente com minhas próprias classes
Maksee

22

Alguns argumentos possíveis para composição:

A composição é um pouco mais de
herança independente da linguagem / estrutura, e o que ela impõe / requer / habilita diferirá entre os idiomas em termos de acesso à sub / superclasse e quais implicações de desempenho podem ter métodos virtuais errados, etc. A composição é bastante básica e requer muito pouco suporte a idiomas e, portanto, implementações em diferentes plataformas / estruturas podem compartilhar padrões de composição mais facilmente.

A composição é uma maneira muito simples e tátil de construir objetos
A herança é relativamente fácil de entender, mas ainda não é tão facilmente demonstrada na vida real. Muitos objetos na vida real podem ser divididos em partes e compostos. Digamos que uma bicicleta possa ser construída usando duas rodas, uma estrutura, um assento, uma corrente etc. Explicado facilmente pela composição. Enquanto em uma metáfora da herança, você poderia dizer que uma bicicleta estende um monociclo, um tanto viável, mas ainda muito mais distante da imagem real do que da composição (obviamente, este não é um exemplo muito bom de herança, mas o ponto permanece o mesmo). Até a palavra herança (pelo menos a maioria dos falantes de inglês dos EUA, eu esperaria) invoca automaticamente um significado ao longo das linhas "Algo passado de um parente falecido", que tem alguma correlação com seu significado no software, mas ainda se encaixa pouco a pouco.

A composição é quase sempre mais flexível
Ao usar a composição, você sempre pode definir seu próprio comportamento ou simplesmente expor essa parte de suas partes compostas. Dessa forma, você não enfrenta nenhuma das restrições que podem ser impostas por uma hierarquia de herança (virtual versus não virtual etc.)

Assim, pode ser porque a composição é naturalmente uma metáfora mais simples que possui menos restrições teóricas do que herança. Além disso, esses motivos específicos podem ser mais aparentes durante o tempo de design ou possivelmente se destacarem ao lidar com alguns dos pontos críticos da herança.

Isenção de responsabilidade:
Obviamente não é essa rua clara / de sentido único. Cada projeto merece avaliação de vários padrões / ferramentas. A herança é amplamente usada, tem muitos benefícios e muitas vezes é mais elegante que a composição. Estas são apenas algumas das razões possíveis que se pode usar ao favorecer a composição.


11
"Obviamente, não é essa rua clara / de mão única." Quando a composição não é mais (ou igualmente) flexível que a herança? Eu diria que é uma via de mão única. A herança é apenas açúcar sintático para um caso especial de composição.
weberc2

A composição geralmente não é flexível ao usar uma linguagem que implementa a composição usando interfaces explicitamente implementadas , porque essas interfaces não podem evoluir de uma maneira compatível com versões anteriores ao longo do tempo. #jmtcw
MikeSchinkel

11

Talvez você tenha notado pessoas dizendo isso nos últimos meses, mas isso é conhecido por bons programadores há muito mais tempo. Eu certamente digo isso quando apropriado há cerca de uma década.

O objetivo do conceito é que existe uma grande sobrecarga conceitual para a herança. Quando você está usando herança, cada chamada de método possui um despacho implícito. Se você possui árvores de herança profundas, ou despacho múltiplo, ou (pior ainda) ambos, então descobrir para onde o método específico será despachado em qualquer chamada específica pode se tornar uma PITA real. Torna o raciocínio correto sobre o código mais complexo e torna a depuração mais difícil.

Deixe-me dar um exemplo simples para ilustrar. Suponha que, no fundo de uma árvore de herança, alguém nomeie um método foo. Então alguém aparece e acrescenta foono topo da árvore, mas fazendo algo diferente. (Esse caso é mais comum com herança múltipla.) Agora essa pessoa que trabalha na classe raiz quebrou a classe filho obscura e provavelmente não a percebe. Você poderia ter 100% de cobertura com testes de unidade e não perceber essa quebra, porque a pessoa no topo não pensaria em testar a classe filho, e os testes para a classe filho não pensariam em testar os novos métodos criados no topo . (É certo que existem maneiras de escrever testes de unidade que capturam isso, mas também há casos em que você não pode escrever facilmente testes dessa maneira.)

Por outro lado, ao usar a composição, a cada chamada geralmente fica mais claro para o qual você está enviando a chamada. (OK, se você estiver usando inversão de controle, por exemplo, com injeção de dependência, descobrir para onde a chamada vai também pode ser problemático. Mas geralmente é mais fácil descobrir.) Isso facilita o raciocínio. Como bônus, a composição resulta em métodos segregados um do outro. O exemplo acima não deve acontecer lá, porque a classe filho mudaria para algum componente obscuro, e nunca há uma dúvida sobre se a chamada foofoi destinada ao componente obscuro ou ao objeto principal.

Agora você está absolutamente certo de que herança e composição são duas ferramentas muito diferentes que servem a dois tipos diferentes de coisas. A herança segura carrega uma sobrecarga conceitual, mas quando é a ferramenta certa para o trabalho, carrega menos sobrecarga conceitual do que tentar não usá-la e fazer manualmente o que ela faz por você. Ninguém que sabe o que está fazendo diria que você nunca deve usar herança. Mas certifique-se de que é a coisa certa a fazer.

Infelizmente, muitos desenvolvedores aprendem sobre software orientado a objetos, aprendem sobre herança e depois usam seu novo machado o mais rápido possível. O que significa que eles tentam usar a herança onde a composição era a ferramenta certa. Espero que eles aprendam melhor com o tempo, mas frequentemente isso não acontece até depois de alguns membros removidos, etc. Dizendo-lhes de antemão que é uma má ideia tende a acelerar o processo de aprendizado e reduzir os ferimentos.


3
Tudo bem, acho que faz sentido do ponto de vista de C ++. Isso é algo em que nunca pensei, porque não é um problema no Delphi, que é o que uso na maioria das vezes. (Não há herança múltipla e, se você tiver um método em uma classe base e outro método com o mesmo nome em uma classe derivada que não substitua o método base, o compilador emitirá um aviso, para que você não acabe acidentalmente com este tipo de problema).
Mason Wheeler

11
@Mason: A versão de Anders do Object Pascal (também conhecida como Delphi) é superior ao C ++ e Java quando se trata do frágil problema de herança da classe base. Ao contrário de C ++ e Java, a sobrecarga de um método virtual herdado não está implícita.
bit-twiddler

2
@ bit-twiddler, o que você diz sobre C ++ e Java pode ser dito sobre Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C e qualquer outra linguagem que aprendi que fornece qualquer forma de suporte ao OO. Dito isso, o Google sugere que o C # segue a liderança de Object Pascal.
btilly

3
Isso porque Anders Hejlsberg criou o sabor de Object Pascal da Borland (também conhecido como Delphi) e C #.
precisa saber é o seguinte

8

É uma reação à observação de que os iniciantes em OO tendem a usar herança quando não precisam. Herança certamente não é uma coisa ruim, mas pode ser usada em excesso. Se uma classe precisa apenas de funcionalidade de outra, a composição provavelmente funcionará. Em outras circunstâncias, a herança funcionará e a composição não.

Herdar de uma classe implica muitas coisas. Isso implica que Derivado é um tipo de Base (consulte o princípio de Substituição de Liskov para obter detalhes sangrentos), pois sempre que você usa uma Base, faria sentido usar uma Derivada. Dá acesso derivado aos membros protegidos e funções de membro do Base. É um relacionamento íntimo, o que significa que possui um alto acoplamento e as alterações em uma são mais propensas a exigir alterações na outra.

Acoplamento é uma coisa ruim. Isso torna os programas mais difíceis de entender e modificar. Sendo outras coisas iguais, você sempre deve selecionar a opção com menos acoplamento.

Portanto, se a composição ou a herança farão o trabalho com eficiência, escolha a composição, porque é um acoplamento menor. Se a composição não realizar o trabalho de maneira eficaz e a herança escolher, escolha herança, porque você precisa.


"Em outras circunstâncias, a herança funcionará e a composição não". Quando? A herança pode ser modelada usando o polimorfismo da composição e da interface; portanto, ficaria surpreso se houver alguma circunstância em que a composição não funcione.
weberc2

8

Aqui estão meus dois centavos (além de todos os excelentes pontos já levantados):

IMHO, tudo se resume ao fato de que a maioria dos programadores realmente não recebe herança e acaba exagerando, parcialmente como resultado de como esse conceito é ensinado. Esse conceito existe como uma maneira de tentar dissuadi-los de exagerar, em vez de se concentrar em ensiná-los a fazer o certo.

Qualquer pessoa que tenha passado algum tempo ensinando ou mentorizando sabe que é isso que acontece com frequência, especialmente com novos desenvolvedores que têm experiência em outros paradigmas:

Esses desenvolvedores inicialmente acham que herança é esse conceito assustador. Portanto, eles acabam criando tipos com sobreposições de interface (por exemplo, o mesmo comportamento especificado sem subtipagem comum) e com globais para implementar partes comuns de funcionalidade.

Então (geralmente como resultado de um ensino excessivamente zeloso), eles aprendem sobre os benefícios da herança, mas geralmente são ensinados como a solução mais abrangente para reutilizar. Eles acabam com a percepção de que qualquer comportamento compartilhado deve ser compartilhado por herança. Isso ocorre porque o foco geralmente está na herança da implementação, e não na subtipagem.

Em 80% dos casos, isso é bom o suficiente. Mas os outros 20% são onde o problema começa. Para evitar reescrever e garantir que eles tenham aproveitado o compartilhamento da implementação, eles começam a projetar sua hierarquia em torno da implementação pretendida, em vez das abstrações subjacentes. A "Pilha herda da lista duplamente vinculada" é um bom exemplo disso.

A maioria dos professores não insiste em introduzir o conceito de interfaces neste momento, porque é outro conceito ou porque (em C ++) você precisa falsificá-los usando aulas abstratas e herança múltipla que não é ensinada nesta fase. Em Java, muitos professores não distinguem a "herança múltipla" ou "herança múltipla é ruim" da importância das interfaces.

Tudo isso é agravado pelo fato de que aprendemos muito da beleza de não precisar escrever código supérfluo com herança de implementação, que toneladas de código de delegação simples parecem antinaturais. Portanto, a composição parece confusa, e é por isso que precisamos dessas regras práticas para pressionar os novos programadores a usá-las de qualquer maneira (que também exageram).


Isso é uma verdadeira parte da educação. Você tem os cursos superiores para obter, ou seja, o restante de 20%, mas você, como aluno, acabou de ministrar os cursos básicos e talvez intermediários. No final, você se sente bem ensinado porque fez seus cursos bem (e simplesmente não o vê apenas um alfinete na escada). É apenas uma parte da realidade e nossa melhor aposta é entender seus efeitos colaterais, não atacar os codificadores.
Independente

8

Em um dos comentários, Mason mencionou que um dia estaríamos falando sobre herança considerada prejudicial .

Acredito que sim.

O problema da herança é ao mesmo tempo simples e mortal, não respeita a ideia de que um conceito deve ter uma funcionalidade.

Na maioria dos idiomas OO, ao herdar de uma classe base, você:

  • herdar de sua interface
  • herdar de sua implementação (dados e métodos)

E aqui se torna o problema.

Essa não é a única abordagem, embora 00 idiomas estejam presos a ela. Felizmente, existem interfaces / classes abstratas.

Além disso, a falta de facilidade para fazer o contrário contribui para torná-lo amplamente utilizado: francamente, mesmo sabendo disso, você herdaria de uma interface e incorporaria a classe base por composição, delegando a maioria das chamadas de método? Seria consideravelmente melhor, você seria avisado se de repente um novo método surgisse na interface e tivesse que escolher, conscientemente, como implementá-lo.

Como contraponto, Haskellpermita apenas o uso do Princípio de Liskov ao "derivar" de interfaces puras (chamadas conceitos) (1). Você não pode derivar de uma classe existente, apenas a composição permite incorporar seus dados.

(1) conceitos podem fornecer um padrão sensato para uma implementação, mas como eles não têm dados, esse padrão só pode ser definido em termos dos outros métodos propostos pelo conceito ou em termos de constantes.


7

A resposta simples é: herança tem maior acoplamento que composição. Dadas duas opções, de qualidades equivalentes, escolha a que for menos acoplada.


2
Mas esse é o ponto. Eles não são "equivalentes". A herança permite a substituição e o polimorfismo de Liskov, que são o ponto principal do uso de POO. Composição não.
Mason Wheeler

4
Polimorfismo / LSP não são "o ponto principal" da POO, são uma característica. Também há encapsulamento, abstrações etc. A herança implica um relacionamento "é um", modelos de agregação e um relacionamento "tem um".
Steve

@ Steve: Você pode fazer abstrações e encapsulamentos em qualquer paradigma que suporte a criação de estruturas de dados e procedimentos / funções. Mas o polimorfismo (especificamente se referindo ao envio de métodos virtuais nesse contexto) e a substituição de Liskov são exclusivos da programação orientada a objetos. Foi isso que eu quis dizer com isso.
Mason Wheeler

3
@Mason: A diretriz é "favorecer a composição sobre a herança". De forma alguma isso significa não usar ou mesmo evitar a herança. Tudo isso diz que quando todas as outras coisas forem iguais, escolha a composição. Mas se você precisar de herança, use-a!
Jeffrey Faust

7

Eu acho que esse tipo de conselho é como dizer "prefiro dirigir a voar". Ou seja, os aviões têm todo tipo de vantagens sobre os carros, mas isso vem com uma certa complexidade. Portanto, se muitas pessoas tentam voar do centro da cidade para os subúrbios, o conselho que elas realmente precisam ouvir é que não precisam voar e, de fato, voar apenas tornará as coisas mais complicadas a longo prazo, mesmo que pareça legal / eficiente / fácil a curto prazo. Considerando que, quando você não precisa voar, é geralmente suposto ser óbvio.

Da mesma forma, a herança pode fazer coisas que a composição não pode, mas você deve usá-lo quando precisar, e não quando não precisar. Portanto, se você nunca é tentado a assumir que precisa de herança quando não precisa, não precisa do conselho de "preferir composição". Mas muitas pessoas fazem , e fazer precisam esse conselho.

Deveria estar implícito que, quando você realmente precisa de herança, é óbvio, e você deve usá-la.

Além disso, a resposta de Steven Lowe. Realmente, realmente isso.


11
Eu gosto da sua analogia
barrylloyd

2

A herança não é inerentemente ruim e a composição não é inerentemente boa. São apenas ferramentas que um programador de OO pode usar para projetar software.

Quando você olha para uma classe, ela está fazendo mais do que deveria ( SRP )? Está duplicando a funcionalidade desnecessariamente ( DRY ) ou está excessivamente interessado nas propriedades ou métodos de outras classes ( Feature Envy ) ?. Se a classe está violando todos esses conceitos (e possivelmente mais), ela está tentando ser uma classe de Deus . Existem vários problemas que podem ocorrer ao projetar software, nenhum dos quais é necessariamente um problema de herança, mas que pode criar rapidamente grandes dores de cabeça e dependências frágeis nas quais o polimorfismo também foi aplicado.

A raiz do problema provavelmente será menos a falta de entendimento sobre herança e mais uma das más escolhas em termos de design, ou talvez não o reconhecimento de "odores de código" relacionados a classes que não estão aderindo ao Princípio de Responsabilidade Única . O polimorfismo e a substituição de Liskov não precisam ser descartados em favor da composição. O próprio polimorfismo pode ser aplicado sem depender de herança, todos esses são conceitos bastante complementares. Se aplicado com cuidado. O truque é manter o código simples, limpo e não sucumbir à preocupação excessiva com o número de classes que você precisa criar para criar um design de sistema robusto.

Na medida em que a questão de favorecer a composição sobre a herança, esse é realmente apenas outro caso da aplicação cuidadosa dos elementos de design que fazem mais sentido em termos de solução do problema. Se você não precisa herdar o comportamento, provavelmente não deve, pois a composição ajudará a evitar incompatibilidades e grandes esforços de refatoração posteriormente. Se, por outro lado, você descobrir que está repetindo muito código, de modo que toda a duplicação está centrada em um grupo de classes semelhantes, pode ser que a criação de um ancestral comum ajude a reduzir o número de chamadas e classes idênticas pode ser necessário repetir entre cada aula. Assim, você está favorecendo a composição, mas não está assumindo que a herança nunca é aplicável.


-1

A citação real vem, acredito, do "Java Efetivo" de Joshua Bloch, onde é o título de um dos capítulos. Ele afirma que a herança é segura dentro de um pacote e onde quer que uma classe tenha sido especificamente projetada e documentada para extensão. Ele afirma, como outros observaram, que a herança quebra o encapsulamento porque uma subclasse depende dos detalhes de implementação de sua superclasse. Isso leva, diz ele, à fragilidade.

Embora ele não mencione isso, parece-me que a herança única em Java significa que a composição oferece muito mais flexibilidade do que a herança.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.