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 foo
no 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 foo
foi 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.