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

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

Não estou discutindo que não seja um problema, mas não vou jogar o “sofá” fora por causa disso…

Ps.: Adorei a do sofá… huhauhauhau

[quote=Ruttmann]Começo o tópico com esta - amplamente divulgada - citação, de modo a instigar uma discussão…

Aprendi a programar com Java há cerca de um ano. Sou super novato na área, e apesar de já estar inserido no mercado, ainda não tive a oportunidade de trabalhar com Java(estou em outros projetos, bem ociosos por sinal, Java não é o forte da empresa em que sou estagiário). Aproveitando toda essa ociosidade tenho estudado conceitos mais inerentes à OO em si, do que conceitos que dizem respeito somente a plataforma.

Passei a tarde lendo sobre interfaces, busquei leituras em livros sobre Design Patterns, li apostilas da Caelum, artigos na Internet e enfim, todos terminam com a citação do título deste tópico.

Noto que Interface é um assunto de certo modo polêmico, visto a dificuldade que a maioria dos estudantes têm em aprender este conceito(digo isso por observação própria). Já tinha ouvido falar bem por cima no curso sobre Java que fiz ano passado, e notei até que o professor foi bem negligente quanto a esse aspecto da linguagem.

Após essa tarde de leitura e exercícios, consegui aprender o que considerei importante: Pra que servem Interfaces e como aplicá-las.

Reparei que é muito fácil dimensionar um sistema fazendo bom uso da Interface, e que não é um esforço tão maior quanto dimensionar voltado a Herança fazer um sistema voltado a Interface. Passei por alguns projetos anteriormente e guardei alguns diagramas de classes, pois imaginei que seriam úteis no futuro.

Pois bem, com os tais diagramas em mão, todos dimensionados com total ausência de Interfaces, consegui mudar as projeções para um modo menos acoplado e mais voltado à Interface.

Ao contrário do que pensava, não é difícil olhar para um projeto mal feito e enxergar Interfaces em pontos que o analista só viu heranças funestas.

Agora vem o X da questão…

Se Interface te proporciona tanta praticidade no desenvolvimento e principalmente na manutenção, porque há tantos sistemas sofrendo com inúmeras dificuldades de manutenção?

Porque que diante de tantas maravilhas proporcionadas pelo uso de Interfaces, elas ainda não são usadas? Já conversei com alguns colegas programadores Java com maior experiência e todos sofrem de uma imensa dificuldade na hora de explicar o uso de Interfaces.

E finalmente, consegui ver todos os benefícios do uso das Interfaces. Existe algum porém nessa história toda? Possíveis limitações que as Interfaces podem trazer?[/quote]

Baseando na história que já vivi:
1- O problema maior que os desenvolvedores enfrentam hoje é o fato das empresas pedirem tudo para ontem.
Fica difícil pensar e planejar tais coisas com chefes te pressionando.
“Assim se faz do jeito que dá…”
Isso mesmo, o pessoal faz do jeito que dá pois no fim tanto a empresa recebe do cliente, quando o funcionário recebe seu pagamento.
É polêmico? sim, mas fazer o que? só se o funcionário abrir uma empresa para ele então. rs

2- A maioria dos professores(Não todos, deixo claro antes que alguém queira me dar tamancada), fica enrolando nas aulas ao invés de ir direto
ao ponto e prática. Pelo contrário ficam falando coisas lá de 1900 e cafunda…
Por isso que tem gente que não sabe nada de Interface. E olha que Java Use a Cabeça, explica isso muito fácil.

3- Finalizando o problema todo resultando dessa confusão é que no fim não tem documentação do sistema e todo o conhecimento do mesmo
fica na cabeça dos desenvolvedores.

Agora o as variáveis sobre isso eu não vou entrar em detalhe pois deve render uns 10 TCC de Faculdade.

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

Mas isto é um problema de boas práticas e não da herança. Se alguem escreve errado não é culpa do recurso. Tem coisa aí que recai no mesmo problema usando a composição. Falar que um é melhor que outro não tem cabimento. Tem problemas que resolvo melhor com generics do que com herança. Recai no mesmo conceito.

[quote=juliocbq]
Mas isto é um problema de boas práticas e não da herança. Se alguem escreve errado não é culpa do recurso.[/quote]
Não é culpa do recurso… aliás, espero que a pobrezinha da herança não esteja levando para o lado pessoal :frowning:

Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”

[quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…

[quote=gomesrod][quote=juliocbq]
Mas isto é um problema de boas práticas e não da herança. Se alguem escreve errado não é culpa do recurso.[/quote]
Não é culpa do recurso… aliás, espero que a pobrezinha da herança não esteja levando para o lado pessoal :frowning:

Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Esse preferencia não tem sentido. Cada coisa serve pra resolver um problema. Esse exemplo não tem nada a ver. O outro sujeito ali cometeu um erro com composição também. Vamos “preferir genérics então”!?

[quote=diogogama][quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…[/quote]

Algo para se pensar: e no caso de um veículo anfíbio? Como vocês modelariam isso? Os exemplos triviais são simples demais. (Não estou dizendo que eu faria um Celta com um atributo Carro lá dentro.)

[quote=Ataxexe][quote=diogogama][quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…[/quote]

Algo para se pensar: e no caso de um veículo anfíbio? Como vocês modelariam isso?[/quote]

Troll

[quote=JavaDreams][quote=Ruttmann]Começo o tópico com esta - amplamente divulgada - citação, de modo a instigar uma discussão…

Aprendi a programar com Java há cerca de um ano. Sou super novato na área, e apesar de já estar inserido no mercado, ainda não tive a oportunidade de trabalhar com Java(estou em outros projetos, bem ociosos por sinal, Java não é o forte da empresa em que sou estagiário). Aproveitando toda essa ociosidade tenho estudado conceitos mais inerentes à OO em si, do que conceitos que dizem respeito somente a plataforma.

Passei a tarde lendo sobre interfaces, busquei leituras em livros sobre Design Patterns, li apostilas da Caelum, artigos na Internet e enfim, todos terminam com a citação do título deste tópico.

Noto que Interface é um assunto de certo modo polêmico, visto a dificuldade que a maioria dos estudantes têm em aprender este conceito(digo isso por observação própria). Já tinha ouvido falar bem por cima no curso sobre Java que fiz ano passado, e notei até que o professor foi bem negligente quanto a esse aspecto da linguagem.

Após essa tarde de leitura e exercícios, consegui aprender o que considerei importante: Pra que servem Interfaces e como aplicá-las.

Reparei que é muito fácil dimensionar um sistema fazendo bom uso da Interface, e que não é um esforço tão maior quanto dimensionar voltado a Herança fazer um sistema voltado a Interface. Passei por alguns projetos anteriormente e guardei alguns diagramas de classes, pois imaginei que seriam úteis no futuro.

Pois bem, com os tais diagramas em mão, todos dimensionados com total ausência de Interfaces, consegui mudar as projeções para um modo menos acoplado e mais voltado à Interface.

Ao contrário do que pensava, não é difícil olhar para um projeto mal feito e enxergar Interfaces em pontos que o analista só viu heranças funestas.

Agora vem o X da questão…

Se Interface te proporciona tanta praticidade no desenvolvimento e principalmente na manutenção, porque há tantos sistemas sofrendo com inúmeras dificuldades de manutenção?

Porque que diante de tantas maravilhas proporcionadas pelo uso de Interfaces, elas ainda não são usadas? Já conversei com alguns colegas programadores Java com maior experiência e todos sofrem de uma imensa dificuldade na hora de explicar o uso de Interfaces.

E finalmente, consegui ver todos os benefícios do uso das Interfaces. Existe algum porém nessa história toda? Possíveis limitações que as Interfaces podem trazer?[/quote]

Baseando na história que já vivi:
1- O problema maior que os desenvolvedores enfrentam hoje é o fato das empresas pedirem tudo para ontem.
Fica difícil pensar e planejar tais coisas com chefes te pressionando.
“Assim se faz do jeito que dá…”
Isso mesmo, o pessoal faz do jeito que dá pois no fim tanto a empresa recebe do cliente, quando o funcionário recebe seu pagamento.
É polêmico? sim, mas fazer o que? só se o funcionário abrir uma empresa para ele então. rs

2- A maioria dos professores(Não todos, deixo claro antes que alguém queira me dar tamancada), fica enrolando nas aulas ao invés de ir direto
ao ponto e prática. Pelo contrário ficam falando coisas lá de 1900 e cafunda…
Por isso que tem gente que não sabe nada de Interface. E olha que Java Use a Cabeça, explica isso muito fácil.

3- Finalizando o problema todo resultando dessa confusão é que no fim não tem documentação do sistema e todo o conhecimento do mesmo
fica na cabeça dos desenvolvedores.

Agora o as variáveis sobre isso eu não vou entrar em detalhe pois deve render uns 10 TCC de Faculdade.[/quote]

Com certeza o fato que mais pesa para as más práticas dentro das empresas é a questão do tempo. Outro fato é que tem muita empresa que coloca o programador pra fazer análise, outra coisa totalmente errada.

Mas o problema principal é ver empresas com analistas e programadores, com suas funções bem definidas, onde o analista não faz bom uso destes recursos.

Por exemplo, aqui onde sou estagiário os analistas tem muitoooo tempo pra fazer as especificações funcionais e técnicas pra um projeto. Conseguem trabalhar folgados, sem pressão. E mesmo assim sai um monte de porcaria, coisas que eu - um estagiário com pouquíssima experiência na área - reparo.

É esse tipo de coisa que não dá pra entender…

[quote=Ataxexe][quote=diogogama][quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…[/quote]

Algo para se pensar: e no caso de um veículo anfíbio? Como vocês modelariam isso? Os exemplos triviais são simples demais. (Não estou dizendo que eu faria um Celta com um atributo Carro lá dentro.)[/quote]

Não são triviais. Se voce modelar corretamente e usar o artificio correto a coisa fica simples. Esse é o objetivo(não complicar).

Eu herdaria veiculo sem nenhum problema. Desde que veiculo suprisse a base de veiculo_anfibio.

[quote=juliocbq][quote=Ataxexe][quote=diogogama][quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…[/quote]

Algo para se pensar: e no caso de um veículo anfíbio? Como vocês modelariam isso? Os exemplos triviais são simples demais. (Não estou dizendo que eu faria um Celta com um atributo Carro lá dentro.)[/quote]

Não são triviais. Se voce modelar corretamente e usar o artificio correto a coisa fica simples. Esse é o objetivo(não complicar).

Eu herdaria veiculo sem nenhum problema. Desde que veiculo suprisse a base de veiculo_anfibio.[/quote]

E se você precisasse tratá-lo como um veículo aquático também? Aí você precisaria achar um comportamento e usar interfaces (talvez nos dois). A herança aqui pode ser usada, mas como o Java não tem herança múltipla, a pessoa que for modelar precisa pensar bem antes de fazer uma loucura que engesse todo o domínio. (Os mixins do ruby fazem um excelente trabalho em casos assim.)

[quote=Ataxexe][quote=juliocbq][quote=Ataxexe][quote=diogogama][quote=gomesrod]
Mas esse exemplo dentro da API do Java é Perfect. É exatamente o tipo de caso ao qual se refere o “Prefira composição à herança”[/quote]

Não acredito que você usará:

public class Celta {
         private Carro carro;
}

então não é certo dizer “Prefira composição à herança”… São duas coisas diferentes… O correto é dizer “Use corretamente a herança e use corretamente a composição” e não valorizar um em detrimento do outro…[/quote]

Algo para se pensar: e no caso de um veículo anfíbio? Como vocês modelariam isso? Os exemplos triviais são simples demais. (Não estou dizendo que eu faria um Celta com um atributo Carro lá dentro.)[/quote]

Não são triviais. Se voce modelar corretamente e usar o artificio correto a coisa fica simples. Esse é o objetivo(não complicar).

Eu herdaria veiculo sem nenhum problema. Desde que veiculo suprisse a base de veiculo_anfibio.[/quote]

E se você precisasse tratá-lo como um veículo aquático também? Aí você precisaria achar um comportamento e usar interfaces (talvez nos dois). A herança aqui pode ser usada, mas como o Java não tem herança múltipla, a pessoa que for modelar precisa pensar bem antes de fazer uma loucura que engesse todo o domínio. (Os mixins do ruby fazem um excelente trabalho em casos assim.)[/quote]

Uai, mas foi isso que eu disse a uns 2 posts atras. Com interfaces voce pode criar um modelo e uma hierarquia robusta.

Eu só estou questionando o título do tópico. “Prefira composição ao invés de herança”. Isso não é solução de problemas.

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

Não foi bem uma conversão visto que Soma e Subtração continuam herdando da interface Operação.

Você apenas introduziu composição sob o pretexto que no futuro ela poderá compensar o fato do modelo ter ficado 100% mais complexo hoje.

[quote=juliocbq]
Esse preferencia não tem sentido. Cada coisa serve pra resolver um problema. Esse exemplo não tem nada a ver. O outro sujeito ali cometeu um erro com composição também. Vamos “preferir genérics então”!?[/quote]

Como eu disse, essa é uma expressão didática, ela não quer dizer: “não use herança”. Ela quer dizer: “na dúvida não use herança” ou “nunca use herança quando deveria usar composição”.

O que eu acho estranho nessa discussão é o fato de vocês continuarem negando que são conceitos normalmente confundidos e que precisam diversas vezes serem explicados, sim, porque você lê e muito que herança é ótima para reaproveitamento de código, mas não é. Herança é ótimo quando você tem comportamentos na mesma família e não pra aproveitamento de código.

[quote=YvGa][quote=juliocbq]
Esse preferencia não tem sentido. Cada coisa serve pra resolver um problema. Esse exemplo não tem nada a ver. O outro sujeito ali cometeu um erro com composição também. Vamos “preferir genérics então”!?[/quote]

Como eu disse, essa é uma expressão didática, ela não quer dizer: “não use herança”. Ela quer dizer: “na dúvida não use herança” ou “nunca use herança quando deveria usar composição”.

O que eu acho estranho nessa discussão é o fato de vocês continuarem negando que são conceitos normalmente confundidos e que precisam diversas vezes serem explicados, sim, porque você lê e muito que herança é ótima para reaproveitamento de código, mas não é. Herança é ótimo quando você tem comportamentos na mesma família e não pra aproveitamento de código.[/quote]

Para mim o “Prefira” ai no título é bem claro, e me desculpe mas até agora não vi a vantagem de um para o outro.
Eu andei dando uma googlada nesse meio tempo e percebi que todos os artigos que encontrei falando desse mesmo tema são todos cópias descaradas de um postado num site da ibm. Ora.

Ninguém teve a decencia de questionar isso?
Falam a respeito de acoplamento mas entre as classes da hierarquia isso não tem a menor diferença.
Que uma simples alteração na superclasse pode propagar problemas. Mas é claro que pode, aliás esse é o objetivo dela. Propagar alterações nas subclasses. Agora o problema está nas mãos de quem escreve o modelo e não no recurso.

A herança é boa para as duas coisas que citou. Ela simplifica o modelo e reutiliza, mas tem que saber fazer, como qualquer outra coisa no mundo.

[quote=YvGa][quote=juliocbq]
Esse preferencia não tem sentido. Cada coisa serve pra resolver um problema. Esse exemplo não tem nada a ver. O outro sujeito ali cometeu um erro com composição também. Vamos “preferir genérics então”!?[/quote]

Como eu disse, essa é uma expressão didática, ela não quer dizer: “não use herança”. Ela quer dizer: “na dúvida não use herança” ou “nunca use herança quando deveria usar composição”.

O que eu acho estranho nessa discussão é o fato de vocês continuarem negando que são conceitos normalmente confundidos e que precisam diversas vezes serem explicados, sim, porque você lê e muito que herança é ótima para reaproveitamento de código, mas não é. Herança é ótimo quando você tem comportamentos na mesma família e não pra aproveitamento de código.[/quote]

Acho que é confundido tanto quanto qualquer coisa mal estudada…

volto a dizer não se deve ensinar que “na dúvida use composição” porque aí sim todos vão achar que devem usar composição em todos os momentos… o correto de ensinar é que “na dúvida estude mais ou pergunta pra quem não tem dúvidas”…

Quem mais precisa de expressão didática ainda está aprendendo OO e certamente não vai saber quando usar um ou outro, logo, pra eles as frases têm o mesmo significado.

Tentei explicar mais ou menos os perigos da herança aqui:

https://dl.dropboxusercontent.com/u/16755042/blog/unbelievable-exception/posts/cuidado-com-a-heranca.html

É um blog que estou montando e ainda está em fase beta (põe beta nisso). Coloquei lá porque era muita coisa pra escrever (e eu ainda vou dar umas editadas depois).

[quote=Ataxexe]Tentei explicar mais ou menos os perigos da herança aqui:

https://dl.dropboxusercontent.com/u/16755042/blog/unbelievable-exception/posts/cuidado-com-a-heranca.html

É um blog que estou montando e ainda está em fase beta (põe beta nisso). Coloquei lá porque era muita coisa pra escrever (e eu ainda vou dar umas editadas depois).[/quote]

Eu ia começar a responder as vantagens que vejo no exemplo que dei, mas seu artigo já cobre tudo isso.

[quote=Ataxexe]Tentei explicar mais ou menos os perigos da herança aqui:

https://dl.dropboxusercontent.com/u/16755042/blog/unbelievable-exception/posts/cuidado-com-a-heranca.html

É um blog que estou montando e ainda está em fase beta (põe beta nisso). Coloquei lá porque era muita coisa pra escrever (e eu ainda vou dar umas editadas depois).[/quote]

Muito bom!

Outro exemplo classico de como a herança pode enganar é o quadrado e o retangulo. Um quadrado é um retangulo, veja que ele passa no teste do é um.

Se o retangulo tem o setAltura e setComprimento, e voce herda o quadrado do retangulo, pois afinal um quadrado é um retangulo, voce começa a ter problemas. Porque se voce mudar a altura de 12 pra 10, enquanto voce nao mudar o comprimento voce não tem um quadrado. Pra resolver isso voce pode, ao alterar a altura fazer com seja alterado tambem o comprimento. Mas esse não é um comportamento esperado pelo cliente da classe. Ou você pode no classe filha criar um metodo setLado. Mas de um jeito ou de outro o princípio de Liskov pro buraco.

Se voce nao herdar o quadrado do retangulo, mas fazer com que um quadrado use um retangulo e tenha seu setLado, chamando setAltura e setComprimento do retangulo, você tem a sua implementação do quadrado independente do retangulo (como deve ser), sem criar no usuário do seu código a ilusão de que ele pode usar um como se fosse o outro.

Talvez seja eu quem não esteja enxergando tamanha simplicidade, mas o conceito de herança é muito complicado e fugir dele é sim uma ótima dica. Vamos abolir a herança? Não, mas vamos ter muito cuidado ao usá-la.