Composição sobre herança, mas


13

Estou tentando me ensinar engenharia de software e encontrar algumas informações conflitantes que estão me confundindo.

Eu tenho aprendido OOP e o que são classes / interfaces abstratas e como usá-las, mas depois estou lendo que é preciso 'favorecer a composição sobre a herança'. Entendo que composição é quando uma classe compõe / cria um objeto de outra classe para utilizar / interagir com a funcionalidade desse novo objeto.

Então, minha pergunta é ... Não devo usar classes e interfaces abstratas? Para não criar uma classe abstrata e estender / herdar a funcionalidade dessa classe abstrata nas classes concretas e, em vez disso, apenas compor novos objetos para usar a funcionalidade da outra classe?

Ou devo usar a composição AND herdar de classes abstratas; usando os dois em conjunto um com o outro? Em caso afirmativo, você poderia fornecer alguns exemplos de como isso funcionaria e seus benefícios?

Como me sinto mais à vontade com o PHP, estou usando-o para aprimorar minhas habilidades em OOP / SE antes de passar para outras linguagens e transferir minhas habilidades recém-adquiridas em SE; portanto, os exemplos usando o PHP seriam muito apreciados.


2
"Composição de favor sobre herança" é uma idéia boba que faz exatamente tanto sentido quanto "serras de favor sobre brocas". São duas ferramentas diferentes que são apropriadas para uso em dois casos de uso diferentes, e os momentos em que você pode realmente usar qualquer um deles são realmente muito raros.
Mason Wheeler

2
"Favor X mais Y" não significa nunca use Y, basta pensar sobre se X seria melhor
Richard Tingle

2
"Favorecer a composição sobre a herança" é expresso com mais precisão como "Nunca, nunca, nunca use herança de classe", com o esclarecimento de que implementar uma interface não é herança e se o idioma escolhido suportar apenas classes abstratas, não interfaces, herdando 100% A classe abstrata também pode ser vista como "não herança".
David Arno

1
@MasonWheeler, não há casos de uso válidos para herança. É pelo menos uma característica "má" como "ir".
David Arno

1
@DavidArno Isso é simplesmente ridículo. A herança é um dos recursos mais úteis e produtivos já desenvolvidos em toda a história da programação. Como em qualquer coisa útil, existem várias maneiras de abusar, mas, quando usado adequadamente, aumenta enormemente o poder e a produtividade do seu trabalho.
Mason Wheeler

Respostas:


19

Composição sobre herança significa que, quando você deseja reutilizar ou estender a funcionalidade de uma classe existente, geralmente é mais apropriado criar outra classe que 'embrulhe' a classe existente e use sua implementação internamente. O padrão Decorator é um exemplo disso.

A herança não é uma boa maneira padrão de lidar com cenários de reutilização, porque geralmente você deseja usar apenas parte de uma funcionalidade de classe base e a subclasse não pode suportar todo o contrato da classe base de uma maneira que satisfaça a substituição de Liskov princípio .

O método de modelo é um bom exemplo de caso em que a herança é apropriada.

Veja esta resposta para obter orientações sobre como escolher entre composição e herança.


+1 por apontar que, no caso de reutilização parcial de uma classe, a parte não utilizada é mais limpa com a composição do que com a herança.
Lawrence

4

Eu não estou familiarizado o suficiente com o PHP para dar exemplos concretos nessa linguagem, mas aqui estão algumas diretrizes que eu achei úteis.

As interfaces / características são úteis para definir o comportamento em muitas classes que podem ter muito pouco em comum.

Por exemplo, se você estiver trabalhando em uma API JSON, poderá definir uma interface que especifique dois métodos: "toJson" e "toStatusCode". Isso permitiria que você escrevesse um auxiliar que pudesse pegar qualquer objeto que implementasse essa interface e convertê-lo em uma resposta Http.

Na maioria das vezes, isso é preferível à herança direta, porque é menos provável que sofra do frágil problema da classe base, pois tende a hierarquias de classe rasas.

Herança direta é melhor pensada como algo que você faz quando há razões convincentes para fazê-lo, em vez de algo que você faz, a menos que haja razões convincentes para não fazê-lo.

Em idiomas que não suportam implementações padrão em interfaces, pode ser uma maneira razoável de compartilhar um conjunto de funcionalidades básicas, mas geralmente restringe bastante o que você pode fazer com as subclasses (herança múltipla, quando disponível, raramente vale a pena) . Muitas vezes, acho que vale a pena escrever os métodos de redirecionamento padrão para usar a composição.

A composição deve ser sua estratégia padrão. Na medida do possível, componha com interfaces e passe-as como argumentos construtores. Isso facilitará sua vida ao escrever testes.

Evite trabalhar com singletons globais o máximo possível, pois comparar a saída esperada e a real é uma dor certa se parte da saída depender do estado do relógio do sistema.

Naturalmente, isso nem sempre é possível. Por exemplo, a estrutura da Web Play fornece uma biblioteca JSON que é basicamente uma linguagem específica do domínio. Alterar isso seria uma tarefa importante, da qual substituir as chamadas para 'Json.prettyPrint' seria apenas uma parte muito menor.


1

Composição é quando uma classe oferece alguma funcionalidade instanciando uma classe (possivelmente interna) que já implementa essa funcionalidade, em vez de herdar dessa classe.

Portanto, por exemplo, se você tem uma classe que modela um navio, e agora lhe dizem que seu navio deve oferecer um heliponto, não é natural derivar seu navio de um heliponto (duh!), Você deve ter seu navio contém uma classe de heliponto e a expõe por algum Ship.getHelipad()método.

Nos anos anteriores (há uma década ou mais), as pessoas costumavam ver a herança como uma maneira rápida e fácil de agregar funcionalidades; portanto, havia muitos exemplos do tipo "nave que herdou do heliporto", que eram, obviamente, muito ruins.

Mas o ditado "Favorecer a composição sobre a herança" foi cuidadosamente formulado para deixar claro que isso é apenas uma sugestão, não uma regra. O autor do ditado teve o cuidado de não dizer algo como " nunca usarás herança, apenas composição". Isso está basicamente chamando a atenção da comunidade de engenharia de software para o fato de a herança ter sido usada em excesso, enquanto em muitos casos a composição produz projetos mais claros, elegantes e sustentáveis ​​do que a herança.

Portanto, essencialmente, o ditado "Favorecer a composição sobre a herança" sugere que sempre que você se deparar com o "herdar ou compor?" pergunta, você deve pensar bem qual é a estratégia mais adequada e que há mais chances de que a estratégia mais adequada acabe sendo composição, não herança.

Mas como essa não é uma regra, você também deve ter em mente que há muitos casos em que a herança é mais natural. Se você usar composição lá onde deveria ter usado herança, muitos males ocorrerão no seu código.

Voltando ao exemplo da nave, se sua nave precisar oferecer uma interface para interagir com a FloatingMachine, é mais natural derivá-la de uma FloatingMachineclasse abstrata , que por sua vez pode ser derivada de outra Machineclasse abstrata .

Aqui está a regra de ouro para a resposta à pergunta composição versus herança:

Minha classe tem um relacionamento "é um" com a interface que precisa expor? Se sim, use herança. Caso contrário, use a composição.

Um navio "é uma" máquina flutuante e uma máquina flutuante "é uma" máquina. Então, herança é perfeitamente adequada para eles. Mas um navio não é, obviamente, um heliporto. Portanto, é melhor compor a funcionalidade do heliponto.

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.