Composição X Herança

Herança ou Composição?

Meu professor disse que não deveria utilizar herança (só em projeto que já tenha herança) e sim usar composição em tudo, é verdade isso ou existem casos que devo utilizar a herança em um caso X e composição em um caso Y? também tenho outra dúvida, antes quando utilizava apenas herança tornava as classes super abstratas para não ser instanciadas e dava extends nas classes sub, porém se depois eu utilizar composição vou ser obrigado a deixa as classes super não abstratas para poder instanciá-las na classes sub para fazer a composição?

Depende do caso, qual caso você está enfrentando? Pois ficar no abstrato fica difícil mesmo. No geral, usar herança em dados é ruim, principalmente em situações que levam a mistura de responsabilidades. Por exemplo, posso ser Cliente de uma loja e também ser Funcionário dessa loja, mas não necessariamente meu cadastro será o mesmo só por que os atributos em maioria são os mesmos. Imagina você alterando os seus dados no site da loja e isso refletir no setor de RH? Ou mesmo utilizando outras estratégias que não sejam gravar na mesma tabela, a alteração em um módulo pode impactar em outro, pois são funcionalidades diferentes.

1 curtida

É interessante, pois um dos pilares da Orientação a Objeto é justamente a Herança.
Não sou acadêmico nem trabalho no ramo, como você sou estudante.
Provavelmente você verá sobre o princípio da identidade única para ignorar a herança, entretanto ser simplista e abolir a herança não parece sutil.

De início eu perguntaria ao professor o porquê desta aversão à herança, (exemplos), pois não seria suficiente apenas dizer não faça, mas sim, não faça nestas condições pois os problemas são estes, aqueles e outros.

Citação quanto a abstração:

Fonte da citação:

Não sou a melhor fonte e certamente haverá melhores esclarecimentos sobre este tema.
Minha impressão é que não cabe expurgar a herança da orientação a objeto.

1 curtida

No dia a dia a herança traz o problema do acoplamento forte.
Particularmente gosto usar herança nas interfaces mas realizar a implementação delas utilizando composição.
Embora acaba-se escrevendo mais, ganha-se tempo com manutenções futuras devido ao acoplamento fraco.

3 curtidas

Só não leve isso como religião. Na prática vale o bom senso e a experiência, nem sempre “vantagens” vendidas ao vento pela OOP são vantagens em uma experiência real para sistemas de informações. Tudo tem o outro lado, faz perder o foco em uma determinada funcionalidade durante a manutenção, escrever menos código nem sempre é melhor, é um “menos” que causa uma mistura grande, isso costuma ser problemático.

1 curtida

Eu já li num livro “prefira a Composição à Herança”, basicamente eu entendo que isso significa que, geralmente, uma solução para um dado problema usando Composição será melhor do que uma solução usando a Herança. Na prática é isso que tenho percebido.

Costumo usar a Herança de Classes somente em casos específicos, onde percebo que as chances de ter que alterar aquele código depois é bem pequena; usar a herança tende a diminuir a quantidade de código e a complexidade, e a falta de flexibilidade da herança não é um problema quando o código não precisará ser alterado (e nem estendido além do que já foi) depois.

Mas acho que o melhor uso para a Herança é o que o @staroski falou, usar a “Herança de Tipo”, que é feita quando implementa-se uma Interface, ou quando uma Interface estende outra. Nesses casos, você usufrui livremente do Polimorfismo para reduzir acoplamento, fazer Injeções de Dependência, reutilizar código, etc., fica tudo extremamente flexível! Nas classes você implementa Interfaces, e monta as Classes por Composição.

É interessante também fazer com que seja possível montar a Composição do Objeto em Tempo de Execução, assim ele fica ainda mais flexível porque, durante a execução, você pode montá-lo com Objetos diferentes para obter resultados diferentes. Isso tem haver com a “Injeção de Dependência”, que traz muita flexibilidade embora custe complexidade (aumenta a complexidade e também a quantidade de código, o que infelizmente pode dificultar a manutenção),

Compor os Objetos é muito legal porque eles tornam-se como “Peças” que você pode encaixar umas nas outras para montar o que você quer, podendo montar com “Peças” diferentes se precisar, ou, até mesmo trocar as “Peças” em tempo de execução, tudo muito flexível!

Não é possível instanciar classes Abstratas, então na composição você precisa fazê-las não-abstratas. Entretanto, eu acho que na maioria dos casos, mudar de Herança para Composição não é tão simples quanto fazer a Ex-Subclasse TER-UMA instância da sua Ex-Superclasse; provavelmente você terá que dividir o código da Ex-Superclasse em diferentes Classes, e, as Ex-Subclasses receberão instâncias de algumas dessas Novas Classes.

Links interessantes (o primeiro parece com o seu caso):

2 curtidas

Muito bom, Obrigado a todos que solucionou minha dúvida. :slight_smile:

1 curtida

Não se trata disso, simplesmente usar composição quando se deve usar herança não faz sentido pra mim.
O que é conhecido:

Herança: relacionamento é um; //tem mais uns poréns importantes, mas esta é a base
Composição: tem um;
Interface: se comporta como um.

Quanto a manutenção o que é mal feito vai dar trabalho de qualquer forma, seja com ou sem herança.
Particularmente, continuo achando arbitrário este posicionamento de usar composição quando se deve usar herança.

Não vou me ater ao caso do funcionário que é cliente e funcionário ao mesmo tempo, pois pessoalmente creio que a implementação da herança seria pessoa <- cliente <- funcionário e quanto ao cadastro um requisito não funcional resolveria a situação, mas isto é irrelevante.

Um caso semelhante seria de uma filha, que passa a ser mãe e depois para avó mantendo todos os seus atributos, não falo da identidade pois a definição e o debate sobre esta é bem mais complexa, tanto é que em um banco de dados usamos o “id”.

Embora haja outros fatores inerentes a herança, para o caso das Mulheres, filha que se torna mãe e depois avó eu preferi a herança, pois faz sentido desde que a classificação e ordem de herança seja a correta: Mulher <- Filha <- Mae <- Avo, neste caso.

A implementação a seguir é irrelevante, mas faz uso de Herança, Polimorfismo e Composição (nada novo).

import java.util.Arrays;

public class Teste {

    public static void main(String[] args) {
        Mulher ana = new Filha("Ana", 11),
                maria = new Filha("Maria", 31),
                joana = new Mae("Joana", 47);//mãe não precisaria informar os filhos

        maria = new Mae((Filha) maria, (Filha) ana);//tornando filha em mãe, podendo usar o método estático familiarizar par mão da classe Mulher 
        joana = new Avo((Mae) joana, (Filha) maria);//tornando mãe em avó

        System.out.println();

        Arrays.asList(ana, maria, joana).forEach(mulher -> {
            System.out.println(mulher.getNome() + " é mãe? [" + mulher.isMae()
                    + "] classe ->  " + mulher.getClass().getSimpleName()
                    + (mulher.temMae() ? " ... Sendo filha de : " + ((Filha) mulher).getMae().getNome() : ""));
        });
    }
}

.

public abstract class Mulher {

    protected String nome;
    protected int idade;

    public abstract boolean temMae();
    
    public abstract boolean isMae();

    public int getIdade() {
        return idade;
    }

    public String getNome() {
        return nome;
    }

    
    public static void familiarizarParaMae(Filha filha) {
        filha = new Mae(filha.nome, filha.idade);
    }
    
    public static void familiarizarParaMae(Filha filha, Filha ... filhas) {
        filha = new Mae(filha, filhas);
    }
}

.

public class Filha extends Mulher{

    private Mae mae;
    protected boolean genitora;
    public Filha(String nome, int idade) {
        this.nome = nome;
        this.idade = idade;
        System.out.println(nome+" utilizando o contrutor da classe filha como: "+this.getClass().getSimpleName());
    }

    public Mae getMae() {
        return mae;
    }

    public void setMae(Mae mae) {
        this.mae = mae;
    }

    @Override
    public boolean isMae() {
        return genitora;
    }
    
    @Override
    public boolean temMae(){
        return this.mae != null;
    }
}

.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Mae extends Filha {

    private final List<Mulher> mulheres = new ArrayList<>();

    public Mae(String nome, int idade) {
        super(nome, idade);
        this.genitora = true;
    }
    
    public Mae(Filha filhaQueSeTornaMae, Filha ... filhas) {
        this(filhaQueSeTornaMae.nome, filhaQueSeTornaMae.idade);       
       
        Arrays.stream(filhas).forEach((Filha filha) -> {
            mulheres.add(filha);            
            filha.setMae(Mae.this);
        });
    }

    public List<Mulher> Descendentes(ArrayList<Mulher> filhas) {
        return mulheres;
    }

    public void addDescendentes(Mulher mulher) {
        mulheres.add(mulher);
    }
}

.

public class Avo extends Mae {

    public Avo(String nome, int idade) {
        super(nome, idade);
    }

    public Avo(Mae maeQueSeTornaAvo, Filha... filhas) {
        super(maeQueSeTornaAvo, filhas);
    }
}

Sou grato pelo compartilhamento de experiências pois me agrada aprender e prever situações, mas neste caso me retiro singelamente da discussão não por que já foi resolvida, ou porque possa estar fugindo de uma crítica, mas simplesmente porque não consigo me conformar com a afirmação: “Prefira composição a herança” quando se deve usar herança no lugar de composição.

Té+

1 curtida

Na teoria e em exemplos acadêmicos pode parecer bacana, mas no mundo real em grandes corporações pode ser problemático misturar determinadas coisas, pois os requisitos são dinâmicos, os setores são separados, o mundo não é perfeito com tudo girando em sincronia. Lembrando o que @Douglas-Silva falou, ele costuma usar herança quando percebe que as chances de alteração são pequenas. Então, não é porque está na bíblia que seria indicado usar herança que ele vai seguir isso sabendo que vai trazer problemas de acoplamento. Herança pode ser mais apropriado em requisitos técnicos, como por exemplo componentes.

1 curtida

É normal esse sentimento e você não está errado por pensar assim. Realmente a questão não é trivial e existem casos e casos.

Talvez um dia você caia de paraquedas em um sistema legado, mal implementado e com um acoplamento muito forte devido à herança de classes.

Em tal cenário, qualquer alteração nas classes base acaba afetando diretamente as classes derivadas, nestes casos, tudo fica muito mais fácil de resolver quando se utiliza a herança de interfaces em conjunto com a composição de classes e abstract factories (ou outro mecanismo de inversão de dependência).

Dica:
Pesquise sobre os materiais do Robert C. Martin (Uncle Bob), ele é autor dos livros Clean Code e Clean Coder (Em português se chamam Código Limpo e O Codificador Limpo respectivamente).
Tem bastante material dele na internet referente à princípios SOLID.

2 curtidas

Vou procurar sim, pois preciso melhorar bastante coisa.
Agradeço a indicação e demais recomendações.
:checkered_flag:

1 curtida

Na verdade, se na bíblia estivesse escrito que eu devo usar herança, eu usaria, porque o que tenho aprendido com Deus é que Ele existe, sabe e quer o melhor pra nós, e deixou a bíblia para que possamos aprender (dentre outras coisas) como agir para que tudo vá bem; então eu acredito mais em Deus do que na minha capacidade analítica de determinar qual o melhor caminho a seguir, ou seja, confio mais na sabedoria e amor dEle do que no meu intelecto humano e limitado :slight_smile:

Não sei bem o que você está chamando de “componente” nesse caso, me parece que as pessoas chamam coisas grandes como pakages de componentes (não tenho certeza); na minha cabeça os componentes são as coisas pequenas que montam as coisas grandes, então costumo chamar de Componentes os Objetos que entram na Composição de outros Objetos.

Assim, um Objeto “Roda” é um Componente tido por um Objeto “Carro”.

Nesses casos, realmente me parece que a Herança de Classes se encaixa bem, pois eu posso criar uma “Família de Classes” (classes ligadas entre si pela herança) que estendem “Roda”, para ter vários tipos de Roda diferente. Então, para cada componente de um Carro (como Motor, Banco, Volante, etc.) eu posso criar uma Família de Classes diferente afim de ter variações de cada Componente; depois é só injetar os Componentes que eu precisar no “Carro”.

Isso me lembra o Padrão Strategy, onde você define até mesmo o Comportamento do “Objeto Cliente” por composição, entretanto, a “Estratégia” (que é o componente injetado no Cliente) vem de uma “Família de Algoritmos” que possui várias Estratégias diferentes (nessa Família de Algoritmos usa-se Herança de Classes, de modo que cada Estratégia é uma Classe conectada às outras desta Família por Herança).

2 curtidas

Não era pra levar ao pé da letra, mas tudo bem, entendi sua mensagem nesse sentido.

Um EditText do Android por exemplo, em que o programador pode customizar herdando.

1 curtida

Seu professor está engando …

Não é para não utilizar Herança ou Composição e sim saber utilizar na hora que for necessário e com padrões de projeto fazer o certo.

O problema é que as pessoas fazem errado, acoplam tudo em um classe e se esquecem que cada classe resolve um problema.

Veja, pode ser feito as duas na hora certa, no contexto certo, da maneira certa, mas, sem contexto acho que todas as resposta são muito abrangente e talvez torne sua duvida mais duvida ainda …

1 curtida