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

[quote=diogogama][quote=Ruttmann][quote=Vina][quote=Ruttmann]
Aí está um ponto interessante. Eu ainda não consegui aprender corretamente a questão da composição.

Alguém tem um exemplo rápido de composição pra explicar?

Seria, por exemplo, uma classe Empresa que “tem um” Cliente? Esse cenário seria o caso de uma composição?[/quote]
Sim, segue um exemplo:

class Carro{ private Motor motor; //getters e setters omitidos }
O código acima diz que a minha classe Carro “tem-um” atributo do tipo motor.
Daí, entrando na instância da classe Carro, eu teria acesso ao atributo motor. Assim, eu consigo acessar os métodos e atributos do motor da seguinte forma:

carro = new Carro(); carro.getMotor().partida(); //liga o motor do carro [/quote]

Ah, entendi! Bem do modo como eu estava pensando. Já usei muito isso estudando sem notar que estava usando composição…

Agradeço a todos que entraram na discussão, tirei várias dúvidas e acabei criando novas.

Estou estudando e me esforçando pra tentar um emprego com Java, gosto muito da plataforma e definitivamente é o que quero seguir a médio prazo. Infelizmente onde estou estagiando não tive chance ainda de trabalhar com isso.

Muito obrigado pela discussão levantada, é ótimo ter pontos de vista do pessoal que vivencia essas questões na prática.

Vou continuar meus estudos e se houver mais dúvidas, vou postando aqui.

Abraços! ;)[/quote]

Concorda que vc usar uma:

[code]class Carro {
}

class Celta extends carro {
}
[/code]

é muito diferente de:

class Carro { private Celta celta; }

mesmo que você consiga a mesma solução você perde um dos maiores benefícios da OO que é a herança (lembrando que usada corretamente).[/quote]

Com certeza.

Celta é um Carro e não está contido em um Carro(se bem que tem gente que diz que Celta não é carro né! rsrsrs Mas isso são problemas conceituais.)…

Quanto a essa questão do “tem um” e “é um” estou bem tranquilo. Teoria dos conjuntos é algo que ajuda muito nesse aspecto, estudei isso o semestre passado inteiro na faculdade… :slight_smile:

[quote=Ruttmann]
Com certeza.

Celta é um Carro e não está contido em um Carro(se bem que tem gente que diz que Celta não é carro né! rsrsrs Mas isso são problemas conceituais.)…

Quanto a essa questão do “tem um” e “é um” estou bem tranquilo. Teoria dos conjuntos é algo que ajuda muito nesse aspecto, estudei isso o semestre passado inteiro na faculdade… :)[/quote]

pois é, o problema é que tem uma galera ae com uma nova filosofia que está dizendo que ao invés de usar “é um” usar “tem um”…

Essa é a grande discussão… Eles alegam que você perde acoplamento… o que, em teoria, seria melhor… Mas eu não concordo que isso deva ser regra e sim que existem casos que você possa usar composição e em outros herança e pronto…

Nesse caso Celta é um objeto que não tem nada a ver(tirando o substantivo claro) com o carro. A herança é usada para reaproveitamento de código.

O pior é que já vi artigos falando a mesma coisa pela internet e até hoje não entendi o motivo da comparação.

Sim, mas concorda que uma coisa não tem nada a ver com outra?

[quote=juliocbq]
Sim, mas concorda que uma coisa não tem nada a ver com outra?[/quote]

Sim, concordo plenamente… Porém não entendo qual é do movimento para abolir a herança… rs…

[quote=diogogama][quote=juliocbq]
Sim, mas concorda que uma coisa não tem nada a ver com outra?[/quote]

Sim, concordo plenamente… Porém não entendo qual é do movimento para abolir a herança… rs…[/quote]

Pois é. Até porque focinho de porco não é tomada. É estranho ver algo como use um e não use outro.

Eu consigo poupar muita escrita de codigo usando herança e classes abstratas porque uma complementa a outra na hierarquia a hierarquia.
Acho que existe um grande desentendimento do que é o que nesse tópico e nos outros da internet. Não faz o menor sentido.

[quote=juliocbq][quote=diogogama][quote=juliocbq]
Sim, mas concorda que uma coisa não tem nada a ver com outra?[/quote]

Sim, concordo plenamente… Porém não entendo qual é do movimento para abolir a herança… rs…[/quote]

Pois é. Até porque focinho de porco não é tomada. É estranho ver algo como use um e não use outro.

Eu consigo poupar muita escrita de codigo usando herança e classes abstratas porque uma complementa a outra na hierarquia a hierarquia.
Acho que existe um grande desentendimento do que é o que nesse tópico e nos outros da internet. Não faz o menor sentido.[/quote]

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=YvGa][quote=juliocbq][quote=diogogama][quote=juliocbq]
Sim, mas concorda que uma coisa não tem nada a ver com outra?[/quote]

Sim, concordo plenamente… Porém não entendo qual é do movimento para abolir a herança… rs…[/quote]

Pois é. Até porque focinho de porco não é tomada. É estranho ver algo como use um e não use outro.

Eu consigo poupar muita escrita de codigo usando herança e classes abstratas porque uma complementa a outra na hierarquia a hierarquia.
Acho que existe um grande desentendimento do que é o que nesse tópico e nos outros da internet. Não faz o menor sentido.[/quote]

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]

E como composição reaproveita código? Isso não possui o menor sentido. Se voce quer reaproveitar código porque um objeto deve ter um comportamento semelhante a outro voce aplica a herança. Inclusive tudo que se usa na java herda object. Infelizmente comparar composição com herança não tem por onde. São coisa com objetivos distintos.

[quote=juliocbq]
E como composição reaproveita código?[/quote]

Ao invés de herdar a sua nova classe da classe que já tem o código que você quer, você a usa como um atributo dessa sua nova, utiliza o comportamento que você precisa, complementa com o que você precisa e não atrela uma a outra, na mesma hierarquia, com o intuito exclusivo de reutilizar aquele código.

Excelentes colocações, YvGa!

[quote=Ruttmann][quote=Vina]
segue um exemplo:

class Carro{ private Motor motor; //getters e setters omitidos }
O código acima diz que a minha classe Carro “tem-um” atributo do tipo motor.
Daí, entrando na instância da classe Carro, eu teria acesso ao atributo motor. Assim, eu consigo acessar os métodos e atributos do motor da seguinte forma:

carro = new Carro(); carro.getMotor().partida(); //liga o motor do carro [/quote]
Ah, entendi! Bem do modo como eu estava pensando. Já usei muito isso estudando sem notar que estava usando composição…[/quote]

Vou fazer uma observação.

Esse foi um exemplo bem claro de composição, só que a problemática toda do herança x composição está justamente nos exemplos não-claros! Naqueles que poderiam gerar alguma confusão, em que alguém acaba implementando como Herança algo que deveria ser uma Composição.

Vou dar um exemplo “semi-real” (Foi em um projeto de verdade, mas é semi-real porque a descrição não é totalmente precisa)
Um framework de comunicação com mainframe possuía uma classe que armazenava os parâmetros necessários para solicitar uma transação. Os parâmetros eram formados por um ou mais registros - imagine a transação como um método, e os registros como objetos que poderiam ser passados no parâmetro.

Esse objeto de parâmetros era mais ou menos assim:

class ParametrosTransacao extends ArrayList<Registro> { .... }

E pronto. Já estava prontinha a lista de parâmetros, graças ao milagre da herança.

ParametrosTransacao parametros = new ParametrosTransacao();
Registro reg1 = ...;
Registro reg2 = ...;
parametros.add(reg1);
parametros.add(reg2);

Esse foi um típico mau uso da herança. O “parâmetros” não É uma lista de registos, ele TEM uma lista de registros. A herança foi aplicada apenas para economizar código!

E que tipo de inconvenientes isso trouxe?

Primeiro:
Em um certo momento, devido a detalhes na implementação do framework que não vêm ao caso, os registros tinham que ser associados a um Tipo. Foi feito mais ou menos assim:

class ParametrosTransacao extends ArrayList<Registro> { private ArrayList<String> tipos; .... public void add(String tipo, Registro registro) { tipos.add(tipo); add(registro); // Esse é o this.add(), herdado do arraylist } } O registro e seu tipo estavam armazenados em estruturas de dados à parte, relacionados apenas pelo seu índice (o elemento 0 da lista de tipos correspondia ao elemento 0 da lista principal, elemento 1 com elemento 1 e assim por diante). Foi uma gambiarra porque a lista-mãe não poderia armazenar os 2 objetos juntos. - se a lista estivesse como um detalhe de implementação seria fácil substituir por uma estrutura de dados mais apropriada.

Segundo:
Aquele monte de métodos exóticos da lista (addAll, removeAll, retainAll, sublist, toArray, etc) estavam disponíveis para o usuário do framework. Apenas o ADD podia ser usado, e pior ainda: apenas o add “melhorado” que recebe dois parâmetros, nunca o add normal de list. Era impossível para o compilador controlar isso, todos os métodos ficam expostos.

Aliás: quando você sente vontade de desabilitar um método na classe filha, são grandes as chances de a herança estar sendo mal-usada.


Falando de um modo geral agora, basicamente existem 2 tipos de herança: herança de interface e herança de implementação.

A herança de interface é a herança “de fato” - quando existe uma relação de É-UM e a classe filha precisa expor todos os métodos da classe mãe.

A herança de implementação é aquela que serve para economizar código herdando um ou mais métodos úteis da classe mãe. ESSE SEGUNDO TIPO É O QUE DEVE SER ELIMINADO EM FAVOR DA COMPOSIÇÃO.

[quote=YvGa][quote=juliocbq]
E como composição reaproveita código?[/quote]

Ao invés de herdar a sua nova classe da classe que já tem o código que você quer, você a usa como um atributo dessa sua nova, utiliza o comportamento que você precisa, complementa com o que você precisa e não atrela uma a outra, na mesma hierarquia, com o intuito exclusivo de reutilizar aquele código.[/quote]

Mas isso não resolve o problema oras. Se eu implementar um filtro numa classe abstrata uso um metodo adequado naquilo que preciso no nível mais baixo da hierarquia. Isso não se consegue com composição.

Mas isso não faz sentido nenhum. String, herda metodos importantes da classe Object no java. Se pensar dessa forma os containers da api java nem teriam saido do papel.

Disse tudo que eu estava tentando explicar com aquela resposta gigantesca e atrapalhada:-)

[quote=juliocbq][quote=gomesrod]

A herança de implementação é aquela que serve para economizar código herdando um ou mais métodos úteis da classe mãe. ESSE SEGUNDO TIPO É O QUE DEVE SER ELIMINADO EM FAVOR DA COMPOSIÇÃO.
[/quote]
Mas isso não faz sentido nenhum. String, herda metodos importantes da classe Object no java. Se pensar dessa forma os containers da api java nem teriam saido do papel.[/quote]
Não vejo nada de errado nisso porque por definição String é um Object - aliás, tudo é - e deve apresentar todos os comportamentos esperados de um Object.

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=gomesrod][quote=juliocbq][quote=gomesrod]

A herança de implementação é aquela que serve para economizar código herdando um ou mais métodos úteis da classe mãe. ESSE SEGUNDO TIPO É O QUE DEVE SER ELIMINADO EM FAVOR DA COMPOSIÇÃO.
[/quote]
Mas isso não faz sentido nenhum. String, herda metodos importantes da classe Object no java. Se pensar dessa forma os containers da api java nem teriam saido do papel.[/quote]
Não vejo nada de errado nisso porque por definição String é um Object - aliás, tudo é - e deve apresentar todos os comportamentos esperados de um Object.[/quote]

Então você está concordando comigo que a composição não substitui a herança.

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=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=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]
Eu não disse que herança tem haver com manter fraco o acoplamento, muito pelo contrário a herança é muito positiva do ponto de vista do polimorfismo mas cria um acoplamento perigoso, esse assunto é amplamente discutido nos livros Effective Java e Design Patterns.

Com relação ao que eu disse é que a herança viola o princípio de manter fraco o acoplamento e isso é fato!
Abraços