"Prefira composição ao invés de herança"

[quote=juliocbq][quote=joaoabi]O título do Post que diz (“Prefira composição ao invés de herança”), que conforme o autor disse é uma frase bastante divulgada já explica um pouco a questão,
de verdade quando os livros e apostilas dizem que devemos preferir a composição não quer dizer que esta substitui a herança.

“Uma coisa é uma coisa outra coisa é outra coisa.”

A herança apresenta alguns problemas como por exemplo

I - O encapsulamento entre classes e subclasses é fraco (o acoplamento é forte)
II - Mudar uma superclasse pode afetar todas as subclasses

Isso vai contra um princípio básico de projeto O-O. (manter fraco acoplamento). Às vezes um objeto precisa ser de uma classe diferente em momentos diferentes.
Com herança, a estrutura está parafusada no código e não pode sofrer alterações facilmente em tempo de execução logo vemos que
a herança é um relacionamento estático que não muda com tempo.

Por outro lado o uso de composição nos traz:
Em vez de codificar um comportamento static, podemos definir comportamentos menores por padrão e usarmos a composição para definir os comportamentos mais complicados

Via de regra, a composição é melhor do que herança normalmente, porque, permite mudar as associações entre classes em tempo de execução, permite que um objeto assuma mais de um comportamento:

Ao passo que a Herança acopla as classes demais e engessa o programa[/quote]

Manter fraco o acoplamento não tem nada a ver com usar herança. As classes herdadas mantém o acoplamento necessário entre elas(são “herdadas”). O objetivo é que a hierarquia passe a frente as alterações feitas lá em cima.

As classes que são herdadas e as que herdam mantém esse relacionamento específico, tanto é que toda api java utiliza delas. Se voce precisar de acoplamento fraco voce cria um objeto distinto para realizar uma tarefa.

Como disse essa frase é muito divulgada mas eu acabo não concordando com ela. Composição e Herança são usadas para resolver problemas diferentes.

[/quote]

Composição e herança deveriam ser usadas para resolver problemas diferentes, mas na prática não são. Existem sim esse hábito de atrelar comportamentos, que poderiam e deveriam ser independentes uns dos outros, através da herança.

Por exemplo, imagine que eu preciso implementar uma classe que calcula juros, porém antes de calcular os juros de uma dívida eu preciso levantar o valor original dessa divida. Para isso eu já tenho lá a classe DividaOriginal que faz esse levantamento completo. Agora para calcular os juros ao invés de usar o DividaOriginal como um atributo da minha classe CalculoDeJuros, eu para não ter que arrumar o terreno pra isso, refatorar o que for preciso pra fazer essa composição, opto pelo caminho “mais curto” de herdar de DividaOriginal e implementar o CalculoDeJuros na minha subclasse.

Daí você já pode imaginar como ficarão as coisas quando eu precisar calcular correção, multa, acréscimos de serviços de cobrança e etc…

E é desse tipo de coisa que se fala quando se diz “prefira composição ao invés de herança”, não é uma tentativa de abolir a prática da herança, mesmo quando ela é a melhor solução. Ninguém está dizendo que não se deve usar herança nunca.

Por isso eu estranhei bastante quando vocês falaram que não conseguiam ver a relação entre as duas coisas. Evidente que em seus conceitos elas são coisas diferentes, o que me surpreende é o fato de vocês nunca as terem visto confundidas.

Isso não é nem de longe um problema para ser resolvido com herança. DividaOriginal possui objetivos diferentes de CalculoDeJuros. A Herança se aplica em classes que possuem comportamentos semelhantes apenas como complemento e extensão das suas funcionalidades. Voce aplicaria herança em uma delas para implementar um outro mecanismo de se calcular a divida original(poderia ser a “DividaDoJoao”). Como voce nao possui a necessidade dessa extensão, não precisa de herança.

Agora um problema a ser resolvido com herança seria a deu eu possuir 3 tipos de abstrações para representar algum tipo de calculo:

[code]public abstract class Calculo{
abstract int calc(int x, int y);
public void doCalc(int x, int y){
System.out.println("O valor do calculo é: " + String.valueOf(calc(x,y));
}
}

public class Soma extends Calculo{
private int calc(int x, int y){
return x+y;
}
}

public class Subtracao extends Calculo{
private int calc(int x, int y){
return x-y;
}
}

Calculo c = new Subtracao();
c.doCalc(10,7);
[/code]
Não se pode preferir um ao outro porque são coisas que resolvem problemas diferentes. Se aplicar composição aí, seu codigo vai ter o triplo do tamanho e nem mesmo a metade da eficencia deste.

[quote=juliocbq]
Agora um problema a ser resolvido com herança seria a deu eu possuir 3 tipos de abstrações para representar algum tipo de calculo:

[code]public abstract class Calculo{
abstract int calc(int x, int y);
public void doCalc(int x, int y){
System.out.println("O valor do calculo é: " + String.valueOf(calc(x,y));
}
}

public class Soma extends Calculo{
private int calc(int x, int y){
return x+y;
}
}

public class Subtracao extends Calculo{
private int calc(int x, int y){
return x-y;
}
}

Calculo c = new Subtracao();
c.doCalc(10,7);
[/code]
Não se pode preferir um ao outro porque são coisas que resolvem problemas diferentes. Se aplicar composição aí, seu codigo vai ter o triplo do tamanho e nem mesmo a metade da eficencia deste.[/quote]

Esse caso pode ser convertido para composição, da seguinte forma:

interface Operacao {
    int calc(int x, int y);
}

class Soma implements Operacao {

    @Override
    public int calc(int x, int y) {
        return x +y;
    }
} 

class Subtracao implements Operacao {

    @Override
    public int calc(int x, int y) {
        return x - y;
    }
    
}
public class Calculo {
    
    public static void main(String[] args) {
        
        Calculo soma = new Calculo(new Soma());
        soma.calculo(1, 2);
        
        Calculo subtracao = new Calculo(new Subtracao());
        subtracao.calculo(3, 4);
                
    }
    private final Operacao operacao;

    public Calculo(Operacao operacao) {
        this.operacao = operacao;        
    }
    
    public void calculo(int x, int y) {
        System.out.println( "O calculo é " + operacao.calc(x, y));
    }
    
}

Não sei se chega a ser o triplo de código (talvez o dobro?), mas acho que não fica tão mais verboso do que o original.

Acho que é um bom exemplo com as duas coisas podem ser usadas para resolver o mesmo tipo de problema.

As vantagens que vejo nesse caso da composição, é que temos classes com menos responsabilidades aqui.
Uma executa o cálculo e faz algo útil com ele. As outras sabem como fazer o cálculo em si.

Sem falar que podemos criar outras classes executoras de cálculo ( que imprime o resultado numa impressora, por exemplo), recebendo a interface Operacao.
Você poderia utilizar as mesmas classes Soma e Subtracao numa nova classe CalculoImpresso com uma nova implementação do método calculo.

No modelo de herança, porém, imagino que seria necessário extender as subclasses Soma para SomaImpresso e sobrescrever o método doCalc.
O número de classes começa a aumentar exponencialmente.

[quote=YvGa]
A questão não é abolir a herança, embora eu relute a usar e só use quando realmente faz sentido, o que não é tão comum quanto aparenta a princípio.

Ok, composição é composição, herança é herança, tudo muito bonito, mas na prática não é bem assim. O mal uso da herança causa confusão entre o que é um e o que é outro. Usar Celta extends Carro e Cachorro extends Animal é um exemplo óbvio e trivial. O problema é quando as coisas deixam de ser óbvias e quem está implementando acha lá em algum canto do sistema alguma coisa que quase faz o que ele quer, exceto por um detalhe. Então ele vai lá, herda o que ele precisa, altera o que ele quer e segue em frente. Depois vem outro e faz a mesma coisa.

E tudo isso em nome do reaproveitamento de código, e acreditem, fazem isso e muito, inclusive muita gente que diz entender o que é uma coisa e o que é outra. Mas quando foge dos exemplos triviais a coisa fica bem menos óbvia. Aí sim, passam a usar herança quando deviam usar composição, passam a fazer enormes hierarquias de classes paralelas para reaproveitar o código da classe mãe e reaproveitar o intermediário, e o que está entre os dois. E está feita a festa.

Então a frase “use composição ao invés de herança” tem sentido sim. Talvez fique mais clara se colocada como “use composição ao invés de herança se só quiser reaproveitar código”, mas que é bastante válida, ela é sim.[/quote]

Concordo com você que está errado… Mas aí é um problema de boas práticas e não de substituir herança por composição…

Não vai crescer exponencialmente. Se eu quiser a funcionalidade da impressora em todas as outras subclasses eu simplesmente adiciono na classe abstrata. Se não, adiciono outra(no caso somaimpresso ou crio outra classe abstrata para tratar esse problema se julgar que essa funcionalidade não seja pertinente nessa hierarquia). Para esse tipo de problema herança é eficiente(e ainda mais se eu usar interfaces no nível mais baixo para ter um desenho mais robusto):

[code]public abstract class Calculo{
abstract int calc(int x, int y);
public void doCalc(int x, int y){
System.out.println("O valor do calculo é: " + String.valueOf(calc(x,y));
}

public void doCalcPrint(int x, int y){
   System.print.println("O valor do calculo é: " + String.valueOf(calc(x,y));
}

}

ou

public class SomaImpresso extends Soma{
@Override
public void doCalc(int x, int y){
System.print.println("O valor do calculo é: " + String.valueOf(calc(x,y));
}
}

[/code]

Volto a questão, tem que se ter boa prática e não excluir o negócio…

Discordo completamente… Acho que abolir a herança é acabar com um dos mais poderosos recursos do Java. Então quando eu for fazer a classe “Celta”, “Gol”, etc, eu vou compor com carro??? pra que se o método ligar é igual em todas??? tem nem cabimento isso…

Mas esse é o objetivo da herança. Você herdar comportamentos comuns e quando for alterar o comportamento mudar em todos, afinal é herança…

se você acha que determinada classe não deva ser acoplada a nada, então não use a herança, ok… Mas daí substituir por composição??? Sem contar que você compõe um atributo a sua classe, e mudar os métodos do atributo, vc não vai ter que alterar na classe que o compôs (caso utilize esses métodos)? isso abaixa o acoplamento como??? afinal se você alterar a classe que compõe, vai alterar na classe composta tmb…

Que doideira é essa gente??? rs…

Acho que no final estão querendo resolver um problema de boas práticas excluindo um recurso…

Se o cara usa errado Herança e Composição é um problema e ponto.

Não quer dizer que temos que preferir composição a herança…

É como o marido que chega em casa e pega a esposa transando com o amante no sofá e grita com eles: “-Vocês nunca mais irão fazer isso comigo!”. Aí vai e joga o sofá pela janela…

Eu acho que é a mesma coisa…

[quote=diogogama]Acho que no final estão querendo resolver um problema de boas práticas excluindo um recurso…

Se o cara usa errado Herança e Composição é um problema e ponto.

Não quer dizer que temos que preferir composição a herança…

É como o marido que chega em casa e pega a esposa transando com o amante no sofá e grita com eles: “-Vocês nunca mais irão fazer isso comigo!”. Aí vai e joga o sofá pela janela…

Eu acho que é a mesma coisa…[/quote]

rsrsrs… É o caso da herança múltipla no c++. As pessoas não sabem usar e jogam a culpa no recurso. Realmente é um típico caso de boas práticas.

[quote=juliocbq]
Não se pode preferir um ao outro porque são coisas que resolvem problemas diferentes. Se aplicar composição aí, seu codigo vai ter o triplo do tamanho e nem mesmo a metade da eficencia deste.[/quote]

Repare que o exemplo que eu usei foi ilustrativo, e por isso mesmo, simples e óbvio, tanto quanto o Celta extends Carro. Mas na prática você vai lidar com exemplos bem menos óbvios. Sim, eu tambem acho que quem faz esse tipo de coisa deve estudar mais e ao estudar mais vai se deparar com a frase “Prefira composição ao invés de herança” e em todos os livros sérios que eu li essa frase, vinham acompanhando ela exemplos de situações onde a herança é indicada, como no exemplo de Celta é um carro.

Qualquer um que estude OO vai saber que são coisas diferentes para serem usadas em contextos diferentes, a frase é para quem está estudando e ainda não tem isso claro na cabeça.

Eu nunca li um autor sério dizer para abolir a herança. Essa frase é um mapa do caminho para quem está começando, pois diz: se não sabe ao certo o que fazer, use composição.

[quote=YvGa][quote=juliocbq]
Não se pode preferir um ao outro porque são coisas que resolvem problemas diferentes. Se aplicar composição aí, seu codigo vai ter o triplo do tamanho e nem mesmo a metade da eficencia deste.[/quote]

Repare que o exemplo que eu usei foi ilustrativo, e por isso mesmo, simples e óbvio, tanto quanto o Celta extends Carro. Mas na prática você vai lidar com exemplos bem menos óbvios. Sim, eu tambem acho que quem faz esse tipo de coisa deve estudar mais e ao estudar mais vai se deparar com a frase “Prefira composição ao invés de herança” e em todos os livros sérios que eu li essa frase, vinham acompanhando ela exemplos de situações onde a herança é indicada, como no exemplo de Celta é um carro.

Qualquer um que estude OO vai saber que são coisas diferentes para serem usadas em conceitos diferentes, a frase é para quem está estudando e ainda não tem isso claro na cabeça.

Eu nunca li um autor sério dizer para abolir a herança. Essa frase é um mapa do caminho para quem está começando, pois diz: se não sabe ao certo o que fazer, use composição.[/quote]

Só que aí daqui a pouco você vai ter um monte de gente usando composição e não saberão nem do que se trata herança, pois aprenderam da forma errada, aí como o problema é de boas práticas vão usar interface no lugar da herança, aí vão dizer “prefira composição a interface”, e aí chegará o dia que as pessoas vão voltar a programar estrutural, no Java, porque vão dizer “Prefira blocos de códigos a OO”, afinal o problema não é saber ou não… o problema são as boas práticas…

Eu acho que “se não sabe ao certo” aprenda, não use composição, porque se não sabe ao certo, você vai fazer merda…

[quote=juliocbq]Problemas com modelos de apis e bibliotecas você não consegue resolver se somente usar composição. Precisa usar herança. São casos completamente distintos onde um não substitui o outro.

Não há como implementar um toolkit como swing ou awt sem usar herança.

[/quote]

Esses casos também são totalmente pertinentes!

Costumo pensar da seguinte forma: quando o objeto filho pode ser atribuído a uma referência da classe mãe e isso faz sentido em 100% dos casos, então são grandes chances de a herança estar sendo usada corretamente.

Por exemplo:

catch (Throwable t) {
      // .....
}

Aqui eu quero dar o mesmo tratamento para qualquer throwable possível, seja runtime exception, checked exception ou Error. Isso faz sentido (pode não ser o melhor tratamento, mas isso não vem ao caso), porque qualquer que seja a classe do objeto T eu posso tratá-lo como objeto da superclasse Throwable.

Ou isso:

void showComponent(JComponent comp) {
     // ...
}

Posso tranquilamente tratar qualquer componente como um JComponent, isso faz todo o sentido porque eles SÃO JComponents

Agora no exemplo de herança mal-utilizada que eu tinha colocado antes:

void copyList(List origem, List destino) {
//...
}

ParametrosTransacao src = ....;
ParametrosTransacao dst = ....;
copyList(src, dst);

Chamar o método copyList com esses objetos não é possível, eles NÃO PODEM ser tratados como lista. Não se deve chamar neles os métodos normais da lista, apenas o add sobrecarregado. A classe herda de ArrayList apenas para aproveitar sua estrutura interna.

Sabe qual é o seu “problema”? Para você é tão óbvio a diferença de uso da herança e composição, que você sequer acredita que exista razão para confusão, que as pessoas jamais vão usar um quando deveriam usar o outro. Mas esse erro existe, e ele está em toda parte! Só quando vivenciar uma situação dessa na prática é que entenderá o motivo da discussão.

[quote=gomesrod][quote=juliocbq]Problemas com modelos de apis e bibliotecas você não consegue resolver se somente usar composição. Precisa usar herança. São casos completamente distintos onde um não substitui o outro.

Não há como implementar um toolkit como swing ou awt sem usar herança.

[/quote]

Esses casos também são totalmente pertinentes!

Costumo pensar da seguinte forma: quando o objeto filho pode ser atribuído a uma referência da classe mãe e isso faz sentido em 100% dos casos, então são grandes chances de a herança estar sendo usada corretamente.

Por exemplo:

catch (Throwable t) {
      // .....
}

Aqui eu quero dar o mesmo tratamento para qualquer throwable possível, seja runtime exception, checked exception ou Error. Isso faz sentido (pode não ser o melhor tratamento, mas isso não vem ao caso), porque qualquer que seja a classe do objeto T eu posso tratá-lo como objeto da superclasse Throwable.

Ou isso:

void showComponent(JComponent comp) {
     // ...
}

Posso tranquilamente tratar qualquer componente como um JComponent, isso faz todo o sentido porque eles SÃO JComponents

Agora no exemplo de herança mal-utilizada que eu tinha colocado antes:

void copyList(List origem, List destino) {
//...
}

ParametrosTransacao src = ....;
ParametrosTransacao dst = ....;
copyList(src, dst);

Chamar o método copyList com esses objetos não é possível, eles NÃO PODEM ser tratados como lista. Não se deve chamar neles os métodos normais da lista, apenas o add sobrecarregado. A classe herda de ArrayList apenas para aproveitar sua estrutura interna.

Sabe qual é o seu “problema”? Para você é tão óbvio a diferença de uso da herança e composição, que você sequer acredita que exista razão para confusão, que as pessoas jamais vão usar um quando deveriam usar o outro. Mas esse erro existe, e ele está em toda parte! Só quando vivenciar uma situação dessa na prática é que entenderá o motivo da discussão.[/quote]

Uai cara, mas é isso que a gente esta discutindo. O caso da api aí é justamente o de se usar herança e não composição.
Esse problema não é do recurso mas de boas práticas. Se alguém não enxerga como deveria a culpa é do alguém.

Por isso não da para preferir um ao outro. Se preferir a composição no problema errado vai ter a mesma dor de cabeça no outro.

Talvez isso valha para você também:

Responda uma coisa: você faria isso?

class Carro extends ArrayList<Peca> {
// ...
}

[quote=gomesrod]
Sabe qual é o seu “problema”? Para você é tão óbvio a diferença de uso da herança e composição, que você sequer acredita que exista razão para confusão, que as pessoas jamais vão usar um quando deveriam usar o outro. Mas esse erro existe, e ele está em toda parte! Só quando vivenciar uma situação dessa na prática é que entenderá o motivo da discussão.[/quote]

Eu acredito que haja um problema nesse sentido, mas é isso que estou dizendo…

a diferença é que acho que temos que ensinar o certo e dentro das boas práticas e não do jeito que estão querendo resolver…

é o caso do sofá que disse acima…

[quote=gomesrod]

class Carro extends ArrayList<Peca> { // ... } [/quote]

Eu não faria… afinal carro não “é um” list de peças… ele “tem um”…

Para vocês verem como esse problema é facilmente reconhecido:

http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html

Properties nunca deveria herdar Hashtable e o motivo está bem claro, bastando olhar a documentação da classe:

Esses erros acontecem e estão em todos os lugares, inclusive, na própria API do Java.

[quote=Ataxexe]
Esses erros acontecem e estão em todos os lugares, inclusive, na própria API do Java.[/quote]

Sim, não estamos dizendo que não é um problema, só estou dizendo que não é motivo para jogar o “sofá” fora…

[quote=diogogama][quote=gomesrod]

class Carro extends ArrayList<Peca> { // ... } [/quote]
Eu não faria… afinal carro não “é um” list de peças… ele “tem um”…[/quote]
No dia em que você ver isso em uma aplicação real vai entender o motivo de tanta encrenca… e por que é importante falar sobre o abuso da herança. Para alguns simplesmente não é tão óbvio!

[quote=gomesrod][quote=diogogama][quote=gomesrod]

class Carro extends ArrayList<Peca> { // ... } [/quote]
Eu não faria… afinal carro não “é um” list de peças… ele “tem um”…[/quote]
No dia em que você ver isso em uma aplicação real vai entender o motivo de tanta encrenca… e por que é importante falar sobre o abuso da herança. Para alguns simplesmente não é tão óbvio![/quote]

Eu acho que esse problema do uso maluco de herança é parecido com o do abuso do Singleton: a facilidade de se fazer.

É muito mais simples colocar um extends onde não se deveria colocar, assim como é muito simples montar um Singleton. Junte isso aos milhares de estagiários que são contratados para desenvolver sistemas sem a presença de um arquiteto e, muitas vezes, de desenvolvedores experientes e a receita do fracasso está pronta.