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

95 respostas
R

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?

95 Respostas

H

De que adianta ter interfaces e não saber como utilizá-las?

É a mesma coisa que dar caviar a um estudante de computação que mora sozinho e só sabe fazer miojo…

V

Cara, cuidado para não confundir as coisas.

A composição é um relacionamento do tipo “tem-um”. A grosso modo, é um objeto pertencente a outro objeto. Um exemplo é a classe Casa, que tem um ou mais atributos do tipo Janela. Com uma instância da Casa, consigo acessar os métodos e atributos das minhas Janelas, como por exemplo “abrir()”. A composição é um recurso deveras poderoso, mas deve ser usado com cuidado para evitar modelagens mais complexas que o necessário para o sistema em questão.

Já a interface é um contrato. É um relacionamento do tipo “é-um”. Um objeto do tipo String, por exemplo, implementa a interface Comparable. Isso significa que ele deve implementar o método compareTo() para possibilitar comparações entre suas instâncias. Então, String é um Comparable.

Agora, por que muita gente não usa interfaces? Não dá pra saber ao certo. Interfaces são muito boas para obrigar desenvolvedores a seguir um determinado contrato. Mas falha quando é necessário já deixar algum comportamento pronto que será usado pela grande maioria das classes filho. Classes abstratas e interfaces são soluções soluções diferentes para casos diferentes, não dá pra colocar tudo no mesmo saco.

Cuidado também com a falácia de que código bom é código com design patterns. Vários podem se tornar anti-patterns de acordo com a utilização. Coisas triviais podem se tornar complexas e difíceis de manter por causa disso. Vale sempre é usar o bom senso e ter noção clara das vantagens e das desvantagens de usar determinado padrão.

R

Vina:
Cara, cuidado para não confundir as coisas.

A composição é um relacionamento do tipo “tem-um”. A grosso modo, é um objeto pertencente a outro objeto. Um exemplo é a classe Casa, que tem um ou mais atributos do tipo Janela. Com uma instância da Casa, consigo acessar os métodos e atributos das minhas Janelas, como por exemplo “abrir()”. A composição é um recurso deveras poderoso, mas deve ser usado com cuidado para evitar modelagens mais complexas que o necessário para o sistema em questão.

Já a interface é um contrato. É um relacionamento do tipo “é-um”. Um objeto do tipo String, por exemplo, implementa a interface Comparable. Isso significa que ele deve implementar o método compareTo() para possibilitar comparações entre suas instâncias. Então, String é um Comparable.

Agora, por que muita gente não usa interfaces? Não dá pra saber ao certo. Interfaces são muito boas para obrigar desenvolvedores a seguir um determinado contrato. Mas falha quando é necessário já deixar algum comportamento pronto que será usado pela grande maioria das classes filho. Classes abstratas e interfaces são soluções soluções diferentes para casos diferentes, não dá pra colocar tudo no mesmo saco.

Cuidado também com a falácia de que código bom é código com design patterns. Vários podem se tornar anti-patterns de acordo com a utilização. Coisas triviais podem se tornar complexas e difíceis de manter por causa disso. Vale sempre é usar o bom senso e ter noção clara das vantagens e das desvantagens de usar determinado padrão.

Sim claro, existem casos em que o uso de interface não substitui o uso da velha herança. Meu post realmente ficou meio estranho, parece que aprendi a abolir a herança em detrimento da interface, mas não foi isso que quis.

Obviamente que um relacionamento do tipo Pessoa>>PessoaFisica/PessoaJurídica não vai poder ser substituído pelo uso de interfaces. Seria idiota fazer isso na maioria dos casos(salvo exceções)…

A questão que você comentou de que interface “falha quando é necessário já deixar algum comportamento pronto que será usado pela grande maioria das classes filho”, diverge um pouco da minha linha de raciocínio.

Como você falou “a grande maioria das classes descendentes”, ou seja, não são todas. Se não são todas, não seria melhor abrir mão desses comportamentos prontos pelo fato de que a maioria dos comportamentos dos filhos são sobrescritos do comportamento dos pais?

Reitero o fato de que ainda estou aprendendo, mas na maioria dos projetos que peguei deu pra notar uma obvia herança entre várias classes, porém não eram tão fortes a ponto de “imitarem” o mesmo comportamento de suas ascendentes.

Nesse aspecto não seria melhor então reescrever os comportamentos que se repetissem em cada classe? Ou estou falando abobrinha?

A

Respondendo e comentando o que disse

Porque este não é o fator determinante para facilitar a manutenção de um sistema.

Elas são muito usadas sim. Aliás, um dos problemas mais apontados em java é justamente o uso excessivo de interfaces, mesmo quando não era necessário.

Ruttmann:

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?

Como dito acima, o uso em excesso pode causar mais dano do que benefícios. Mas isso vale para tudo na vida que esteja "em excesso".

Ruttmann:

Obviamente que um relacionamento do tipo Pessoa>>PessoaFisica/PessoaJurídica não vai poder ser substituído pelo uso de interfaces. Seria idiota fazer isso na maioria dos casos(salvo exceções)…

Na verdade esse caso não tem nada de óbvio. Pessoa Física e Jurídica são duas coisas bem diferentes apesar de serem chamados Pessoas.
Tem uma thread há um tempo atrás discutindo isso (aliás, tem várias), mas nunca se chegou a um consenso.

Uma última coisa pra finalizar: quando dizem "programem para Interface" não estão falando da palavra chave no java e sim do conceito de contrato.
Você pode programar para interfaces mesmo em javascript, por exemplo.

Y

De fato, algumas vezes você precisa de um comportamento padrão, mas ainda assim você pode (e em alguns casos, deve) abstrair mais um pouco e separar esta implementação padrão daquilo que irá variar para cada uma das implementações. O velho “encapsule o que varia”. Usar interfaces sempre que possível ao invés de herança não te deixa preso aquela implementação, de forma alguma. Se esta implementação padrão mudar, for recomposta, precisar ser decorada, isso em nada afetará o seu contrato de interface.

Y

Existem casos em que não, mas são poucos. A velha questão é o famoso “é um”, se não é um não se usa herança. Isso é obvio em entidades que representam o mundo real e bem menos óbvios quando falamos de código diretamente. A dica que eu uso e que passo adiante é: nunca use herança com o intuito único de reutilizar código.

Como já foi dita aqui, Pessoa Jurídica não é uma pessoa, o fato de se chamar pessoa gera confusão.

Ruttmann:

A questão que você comentou de que interface “falha quando é necessário já deixar algum comportamento pronto que será usado pela grande maioria das classes filho”, diverge um pouco da minha linha de raciocínio.

Como você falou “a grande maioria das classes descendentes”, ou seja, não são todas. Se não são todas, não seria melhor abrir mão desses comportamentos prontos pelo fato de que a maioria dos comportamentos dos filhos são sobrescritos do comportamento dos pais?

Reitero o fato de que ainda estou aprendendo, mas na maioria dos projetos que peguei deu pra notar uma obvia herança entre várias classes, porém não eram tão fortes a ponto de “imitarem” o mesmo comportamento de suas ascendentes.

Nesse aspecto não seria melhor então reescrever os comportamentos que se repetissem em cada classe? Ou estou falando abobrinha?

Não você não deve reescrever os comportamentos para cada filha, mas você não está falando abobrinha. Você pode nesses casos, como eu disse no post anterior, extrair a interface com mais uma abstração, escrever naquilo que seria a usa classe mãe o comportamento padrão e usar a interface por composição ao invés de herança nessa mesma classe para os casos em que variasse a implementação.

V

YvGa, eu acho que é possível também manter um contrato com classes abstratas. Como já foi dito, o conceito de contrato que é importante, seja ele vindo de uma interface ou de uma classe abstrata. Não vejo muito sentido em criar mais uma camada de abstração, a não ser que você queira, de fato, esconder o código de um método não abstrato de uma classe abstrata. E esconder é muito diferente de encapsular.

Y

Vina:
YvGa:

De fato, algumas vezes você precisa de um comportamento padrão, mas ainda assim você pode (e em alguns casos, deve) abstrair mais um pouco e separar esta implementação padrão daquilo que irá variar para cada uma das implementações. O velho “encapsule o que varia”. Usar interfaces sempre que possível ao invés de herança não te deixa preso aquela implementação, de forma alguma. Se esta implementação padrão mudar, for recomposta, precisar ser decorada, isso em nada afetará o seu contrato de interface.

YvGa, eu acho que é possível também manter um contrato com classes abstratas. Como já foi dito, o conceito de contrato que é importante, seja ele vindo de uma interface ou de uma classe abstrata. Não vejo muito sentido em criar mais uma camada de abstração, a não ser que você queira, de fato, esconder o código de um método não abstrato de uma classe abstrata. E esconder é muito diferente de encapsular.

Veja, programar para interface é um dos motivos para você usar a interface em si. Sim, você está certo, programar para interface não significa ter que obrigatoriamente usar interfaces, mas significa programar para um contrato, uma interface publica de uma classe, um facade, etc… Significa não se preocupar com detalhe de implementação.

Sim, nada te obriga, nem te indica que automaticamente deveria, criar a outra abstração. Mas no caso de não fazê-la você deve ter certeza do que está fazendo, porque a partir do momento que você herda de uma classe ao invés de implementar uma interface, você amarrou aquela estrutura nela mesma. Qualquer coisa de fora ou que tenha que sair está presa aquilo.

Aí é um caso de avaliar se isso causará ou não problema. Na dúvida opte pelo mais simples, lembrando que mais simples pode variar de situação pra situação.

G

Por que você tinha que pegar justamente o exemplo mais polêmico ? :slight_smile:

D

A verdade é, em 90% dos casos eu devo deixar de usar herança e passar a usar interface ou composição (isso pra não dizer 100%)?
Estávamos nessa discussão no outro tópico, porém entendo, mas não concordei muito.

Toda a estrutura do java é feita praticamente com heranças e interfaces, como posso dizer que o uso disso não é correto?

Como posso dizer que não é correto usar herança e colocar que meu método recebe um Object? ou uma Collection??? Fica um tanto quanto destoante pra mim.

Assim, acho que devemos programar orientados a interface sim, tomar cuidado para não exagerar como disse o amigo acima, mas nunca, devemos deixar de usar a herança quando necessário, pois se trata de um poderoso recurso.

Será que alguém aqui reescreve o método toString()? e pq isso é possível???

Então não acho que se trate de um anti-patterns, apenas as pessoas precisam aprender o que significa herança, interface e composição e saber aplicá-las, não excluí-las porque não sabem usar corretamente.

Bem, essa é minha opinião, mas digam ae como pensam que posso estar errado.

Abraços.

V

YvGa:

Veja, programar para interface é um dos motivos para você usar a interface em si. Sim, você está certo, programar para interface não significa ter que obrigatoriamente usar interfaces, mas significa programar para um contrato, uma interface publica de uma classe, um facade, etc… Significa não se preocupar com detalhe de implementação.

Sim, nada te obriga, nem te indica que automaticamente deveria, criar a outra abstração. Mas no caso de não fazê-la você deve ter certeza do que está fazendo, porque a partir do momento que você herda de uma classe ao invés de implementar uma interface, você amarrou aquela estrutura nela mesma. Qualquer coisa de fora ou que tenha que sair está presa aquilo.

Aí é um caso de avaliar se isso causará ou não problema. Na dúvida opte pelo mais simples, lembrando que mais simples pode variar de situação pra situação.


Concordo com você. Classes abstratas e interfaces tem aplicações diferentes, embora partam do mesmo conceito da orientação a objetos. Isso confunde um pouco. É importante acabar com a demonização de um recurso em favor de outro. Um engenheiro de software deve ser crítico e avaliar quando e porque usar determinado recurso.

C

Composição = “tem um”

Herança = “é um”

Interface = “se comporta como um”

A frase sugere apenas que você use composição no lugar de herança quando for possível, não vejo uma relação direta com interface.

A

carlos alexandre moscoso:
Composição = “tem um”

Herança = “é um”

Interface = “se comporta como um”

A frase sugere apenas que você use composição no lugar de herança quando for possível, não vejo uma relação direta com interface.

Exatamente, o uso de interfaces é outra história. Você pode muito bem ter composição com classes em vez de interfaces. Está melhor do que usar herança, mas pode ficar ainda melhor se você usar interfaces (não por causa da herança e, sim, por causa da dependência de implementações).

J

Eu leio isso em artigo e não me conformo quando tentam comparar uma coisa com outra. Herança é um artifício com objetivo diferente de composição. Não entendo essa comparação.

D

Eu tmb discordo e acho que é completamente diferente.

R

Ataxexe:
carlos alexandre moscoso:
Composição = “tem um”

Herança = “é um”

Interface = “se comporta como um”

A frase sugere apenas que você use composição no lugar de herança quando for possível, não vejo uma relação direta com interface.

Exatamente, o uso de interfaces é outra história. Você pode muito bem ter composição com classes em vez de interfaces. Está melhor do que usar herança, mas pode ficar ainda melhor se você usar interfaces (não por causa da herança e, sim, por causa da dependência de implementações).

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?

V

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?


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

R

Vina:
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?


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

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! :wink:

D
Ruttmann:
Vina:
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?
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

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! ;)

Concorda que vc usar uma:
class Carro {
}

class Celta extends carro {
}
é 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).

R
diogogama:
Ruttmann:
Vina:
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?
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

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! ;)

Concorda que vc usar uma:
class Carro {
}

class Celta extends carro {
}
é 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).

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... :)

D

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… :)

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…

J

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.

J

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

D

juliocbq:

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

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

J

diogogama:
juliocbq:

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

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

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.

Y

juliocbq:
diogogama:
juliocbq:

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

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

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.

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.

J

YvGa:
juliocbq:
diogogama:
juliocbq:

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

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

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.

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.

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.

Y

juliocbq:

E como composição reaproveita código?

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.

A

Excelentes colocações, YvGa!

G

Ruttmann:
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


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

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.

J

YvGa:
juliocbq:

E como composição reaproveita código?

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.

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.

J

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.

G

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

G

juliocbq:
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.

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.

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.

J

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.

J

gomesrod:
juliocbq:
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.

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.

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.

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

J

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

J

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

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.

J

juliocbq:
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

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.


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

Y

juliocbq:
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

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.

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.

J
YvGa:
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.

Se alguém tem o habito de confundir essas duas coisas eu sugiro que volte a estudar mais, porque não tem por onde. Para voce ter uma ideia do problema, reveja a situação que acabou de me reportar como exemplo:

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.

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:

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);
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.
A
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:
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);
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.

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.

D

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.

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

J
AbelBueno:
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.

Claro que fica bem verboso, mesmo você tendo usado um mecanismo de herança de certa forma com interfaces esse código não tem tanta eficiência como o outro. Porquê? Porque é um típico problema que pode ser resolvido da melhor forma usando herança. Não que a composição não sirva, mas para esse caso não é a melhor solução.

AbelBueno:
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.

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):

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));
    }
}
D

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…

D

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…

D

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…

J

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…

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.

Y

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.

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.

D

YvGa:
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.

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.

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…

G

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.

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.

J

gomesrod:
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.

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.

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.

G

Talvez isso valha para você também:

Responda uma coisa: você faria isso?

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

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.

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…

D

gomesrod:

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

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

A

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.

D

Ataxexe:

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

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

G

diogogama:
gomesrod:

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


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

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!

A

gomesrod:
diogogama:
gomesrod:

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


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

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!

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.

D

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!

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

J

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?

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.

J

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.

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.

G

juliocbq:

Mas isto é um problema de boas práticas e não da herança. Se alguem escreve errado não é culpa do recurso.

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”

D

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”

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…

J

gomesrod:
juliocbq:

Mas isto é um problema de boas práticas e não da herança. Se alguem escreve errado não é culpa do recurso.

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”

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”!?

A

diogogama:
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”

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…

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.)

D

Ataxexe:
diogogama:
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”

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…

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

Troll

R

JavaDreams:
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?

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.

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…

J

Ataxexe:
diogogama:
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”

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…

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.)

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.

A

juliocbq:
Ataxexe:
diogogama:
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”

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…

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.)

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.

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.)

J

Ataxexe:
juliocbq:
Ataxexe:
diogogama:
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”

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…

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.)

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.

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.)

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.

C
AbelBueno:
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:
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);
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.

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.

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.

Y

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”!?

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.

J

YvGa:
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”!?

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.

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.

D

YvGa:
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”!?

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.

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”…

C

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.

A

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).

A

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).

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

Y

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).

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.

J

YvGa:
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).

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.

Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

Essa questão de herança não tem nenhum mistério ou complicação. Se voce tem em mente um modelo onde as hierarquias não são profundas(trocentos níveis), é sólida(tem o comportamento garantido por interfaces) e eficiente(não herda madeira de papel) ela será mais eficiente que a composição por deixar o código simples e enxuto.

*edit aqui - Um quadrado não é um retangulo.

A

juliocbq:
Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

É esquisito, mas não significa que não é usado. Milhares de sistemas por aí são feitos com essas bizarrices e é por isso que a gente alerta pra essas coisas. Cada vez mais o mercado paga menos pra animais fazerem trabalhos que não deveriam fazer e esses monstros binários aparecem por aí.

Um exemplo é um sistema que eu dei manutenção há uns anos e que ainda é usado no Brasil inteiro: o cara criou um framework de Collections!!! Tinha classes pra todos os gostos: SimpleDynaArray, DoubleDynaArray, SimpleSortedDynaArray, DoubleSortedDynaArray. E eu já vi muitos que iniciaram dando manutenção nesse sistema. O cara está aprendendo e com exemplos terríveis. Se ninguém mostrar o erro ele acaba aprendendo errado.

Waterfall é uma merda e ainda é usado e, pior ainda, mascarado em cima de metodologias ágeis com mini waterfalls travestidos de sprints. Isso é mais bizarro ainda do que herdar um quadrado de um retângulo, mas é muito usado também.

Eu passei minha carreira inteira mantendo sistemas mal feitos e, sinceramente, me sinto um papel higiênico. Às vezes eu não acreditava no quão óbvias as cagadas eram, mas existem pessoas que não aprenderam corretamente ou passaram o tempo todo pedindo código pronto em fórum e não se deram ao trabalho de estudar nada. Pra essas pessoas, aquilo era lindo.

Y

juliocbq:

Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

Essa questão de herança não tem nenhum mistério ou complicação. Se voce tem em mente um modelo onde as hierarquias não são profundas(trocentos níveis), é sólida(tem o comportamento garantido por interfaces) e eficiente(não herda madeira de papel) ela será mais eficiente que a composição por deixar o código simples e enxuto.

*edit aqui - Um quadrado não é um retangulo.

Herdar um quadrado de um retangulo é bisonho? Um quadrado não é um retangulo? Um retangulo deriva de um quadrado? Então você está me dizendo que um retangulo é antes de tudo um quadrado?

Você está redondamente (com o perdão do trocadilho) errado. Um quadrado é um retangulo, e a herança seria perfeitamente compreensível não fossem os poréns envolvidos.

O que eu estou dizendo, e como o seu post comprovou, as coisas não são tão cristalinas como vocês querem fazer pensar que são.

J

Ataxexe:
juliocbq:
Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

É esquisito, mas não significa que não é usado. Milhares de sistemas por aí são feitos com essas bizarrices e é por isso que a gente alerta pra essas coisas. Cada vez mais o mercado paga menos pra animais fazerem trabalhos que não deveriam fazer e esses monstros binários aparecem por aí.

Um exemplo é um sistema que eu dei manutenção há uns anos e que ainda é usado no Brasil inteiro: o cara criou um framework de Collections!!! Tinha classes pra todos os gostos: SimpleDynaArray, DoubleDynaArray, SimpleSortedDynaArray, DoubleSortedDynaArray. E eu já vi muitos que iniciaram dando manutenção nesse sistema. O cara está aprendendo e com exemplos terríveis. Se ninguém mostrar o erro ele acaba aprendendo errado.

Waterfall é uma merda e ainda é usado e, pior ainda, mascarado em cima de metodologias ágeis com mini waterfalls travestidos de sprints. Isso é mais bizarro ainda do que herdar um quadrado de um retângulo, mas é muito usado também.

Eu passei minha carreira inteira mantendo sistemas mal feitos e, sinceramente, me sinto um papel higiênico. Às vezes eu não acreditava no quão óbvias as cagadas eram, mas existem pessoas que não aprenderam corretamente ou passaram o tempo todo pedindo código pronto em fórum e não se deram ao trabalho de estudar nada. Pra essas pessoas, aquilo era lindo.

Eu venho mantendo sistemas de cftv escritos com delphi, controle de acesso escrito em java e até firmwares escritos em c para pic 18 ou 16fxx e aqui não temos problemas com essas coisas. Nosso software possui uma hierarquia que representa drivers cada marca de placa de captura. Essa hierarquia tem 3 niveis apenas(uma interface, uma classe abstrata com um metodo generico para escrever e ler da memoria da placa pci e as classes especificas com atributos especificos das placas). Ele já existe ha uns 10 anos e está na ativa até hoje. Sempre que vejo em uma padaria, estabelecimento comercial que seja pergunto se deu problema
algum dia e sempre recebo uma resposta boa. Por isso eu não concordo com essa preferência de recursos. Tem que ter boas práticas para desenvolver uma solução. Minha opinião é usar esse e aquele artifício com maestria, não excluir um em favorecimento de outro.

J

YvGa:
juliocbq:

Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

Essa questão de herança não tem nenhum mistério ou complicação. Se voce tem em mente um modelo onde as hierarquias não são profundas(trocentos níveis), é sólida(tem o comportamento garantido por interfaces) e eficiente(não herda madeira de papel) ela será mais eficiente que a composição por deixar o código simples e enxuto.

*edit aqui - Um quadrado não é um retangulo.

Herdar um quadrado de um retangulo é bisonho? Um quadrado não é um retangulo? Um retangulo deriva de um quadrado? Então você está me dizendo que um retangulo é antes de tudo um quadrado?

Você está redondamente (com o perdão do trocadilho) errado. Um quadrado é um retangulo, e a herança seria perfeitamente compreensível não fossem os poréns envolvidos.

O que eu estou dizendo, e como o seu post comprovou, as coisas não são tão cristalinas como vocês querem fazer pensar que são.

O quadrado não é um retângulo nunca. Um quadrado possui os atributos de largura e altura fixos enquanto um retangulo adiciona um comportamento variável dessas propriedades. Sua modelagem aí é que está quadrada, desculpe.

A

juliocbq:
Ataxexe:
juliocbq:
Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

É esquisito, mas não significa que não é usado. Milhares de sistemas por aí são feitos com essas bizarrices e é por isso que a gente alerta pra essas coisas. Cada vez mais o mercado paga menos pra animais fazerem trabalhos que não deveriam fazer e esses monstros binários aparecem por aí.

Um exemplo é um sistema que eu dei manutenção há uns anos e que ainda é usado no Brasil inteiro: o cara criou um framework de Collections!!! Tinha classes pra todos os gostos: SimpleDynaArray, DoubleDynaArray, SimpleSortedDynaArray, DoubleSortedDynaArray. E eu já vi muitos que iniciaram dando manutenção nesse sistema. O cara está aprendendo e com exemplos terríveis. Se ninguém mostrar o erro ele acaba aprendendo errado.

Waterfall é uma merda e ainda é usado e, pior ainda, mascarado em cima de metodologias ágeis com mini waterfalls travestidos de sprints. Isso é mais bizarro ainda do que herdar um quadrado de um retângulo, mas é muito usado também.

Eu passei minha carreira inteira mantendo sistemas mal feitos e, sinceramente, me sinto um papel higiênico. Às vezes eu não acreditava no quão óbvias as cagadas eram, mas existem pessoas que não aprenderam corretamente ou passaram o tempo todo pedindo código pronto em fórum e não se deram ao trabalho de estudar nada. Pra essas pessoas, aquilo era lindo.

Eu venho mantendo sistemas de cftv escritos com delphi, controle de acesso escrito em java e até firmwares escritos em c para pic 18 ou 16fxx e aqui não temos problemas com essas coisas. Nosso software possui uma hierarquia que representa drivers cada marca de placa de captura. Essa hierarquia tem 3 niveis apenas(uma interface, uma classe abstrata com um metodo generico para escrever e ler da memoria da placa pci e as classes especificas com atributos especificos das placas). Ele já existe ha uns 10 anos e está na ativa até hoje. Sempre que vejo em uma padaria, estabelecimento comercial que seja pergunto se deu problema
algum dia e sempre recebo uma resposta boa. Por isso eu não concordo com essa preferência de recursos. Tem que ter boas práticas para desenvolver uma solução. Minha opinião é usar esse e aquele artifício com maestria, não excluir um em favorecimento de outro.


Bom, você ainda não conseguiu entender que eu não estou abolindo a herança, estou mostrando os problemas do mau uso dela, que é feito na maioria das vezes para reaproveitamento de código e não para estabelecer uma hierarquia consistente seguindo o princípio de substituição de Liskov.

Não duvido que você tenha problemas de orientação a objetos escrevendo firmwares em c. Depois dessa eu parei por aqui.

J

Aproveitando esse exemplo de quadrado herdar um retângulo, esse sim quebra o princípio de Liskov por herdar a ave da galinha.
Fizeram um pequeno artigo sobre:

Y

juliocbq:
YvGa:
juliocbq:

Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

Essa questão de herança não tem nenhum mistério ou complicação. Se voce tem em mente um modelo onde as hierarquias não são profundas(trocentos níveis), é sólida(tem o comportamento garantido por interfaces) e eficiente(não herda madeira de papel) ela será mais eficiente que a composição por deixar o código simples e enxuto.

*edit aqui - Um quadrado não é um retangulo.

Herdar um quadrado de um retangulo é bisonho? Um quadrado não é um retangulo? Um retangulo deriva de um quadrado? Então você está me dizendo que um retangulo é antes de tudo um quadrado?

Você está redondamente (com o perdão do trocadilho) errado. Um quadrado é um retangulo, e a herança seria perfeitamente compreensível não fossem os poréns envolvidos.

O que eu estou dizendo, e como o seu post comprovou, as coisas não são tão cristalinas como vocês querem fazer pensar que são.

O quadrado não é um retângulo nunca. Um quadrado possui os atributos de largura e altura fixos enquanto um retangulo adiciona um comportamento variável dessas propriedades. Sua modelagem aí é que está quadrada, desculpe.

Todo quadrado é também um retângulo e um losango.

J

Ataxexe:
juliocbq:
Ataxexe:
juliocbq:
Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

É esquisito, mas não significa que não é usado. Milhares de sistemas por aí são feitos com essas bizarrices e é por isso que a gente alerta pra essas coisas. Cada vez mais o mercado paga menos pra animais fazerem trabalhos que não deveriam fazer e esses monstros binários aparecem por aí.

Um exemplo é um sistema que eu dei manutenção há uns anos e que ainda é usado no Brasil inteiro: o cara criou um framework de Collections!!! Tinha classes pra todos os gostos: SimpleDynaArray, DoubleDynaArray, SimpleSortedDynaArray, DoubleSortedDynaArray. E eu já vi muitos que iniciaram dando manutenção nesse sistema. O cara está aprendendo e com exemplos terríveis. Se ninguém mostrar o erro ele acaba aprendendo errado.

Waterfall é uma merda e ainda é usado e, pior ainda, mascarado em cima de metodologias ágeis com mini waterfalls travestidos de sprints. Isso é mais bizarro ainda do que herdar um quadrado de um retângulo, mas é muito usado também.

Eu passei minha carreira inteira mantendo sistemas mal feitos e, sinceramente, me sinto um papel higiênico. Às vezes eu não acreditava no quão óbvias as cagadas eram, mas existem pessoas que não aprenderam corretamente ou passaram o tempo todo pedindo código pronto em fórum e não se deram ao trabalho de estudar nada. Pra essas pessoas, aquilo era lindo.

Eu venho mantendo sistemas de cftv escritos com delphi, controle de acesso escrito em java e até firmwares escritos em c para pic 18 ou 16fxx e aqui não temos problemas com essas coisas. Nosso software possui uma hierarquia que representa drivers cada marca de placa de captura. Essa hierarquia tem 3 niveis apenas(uma interface, uma classe abstrata com um metodo generico para escrever e ler da memoria da placa pci e as classes especificas com atributos especificos das placas). Ele já existe ha uns 10 anos e está na ativa até hoje. Sempre que vejo em uma padaria, estabelecimento comercial que seja pergunto se deu problema
algum dia e sempre recebo uma resposta boa. Por isso eu não concordo com essa preferência de recursos. Tem que ter boas práticas para desenvolver uma solução. Minha opinião é usar esse e aquele artifício com maestria, não excluir um em favorecimento de outro.


Bom, você ainda não conseguiu entender que eu não estou abolindo a herança, estou mostrando os problemas do mau uso dela, que é feito na maioria das vezes para reaproveitamento de código e não para estabelecer uma hierarquia consistente seguindo o princípio de substituição de Liskov.

Não duvido que você tenha problemas de orientação a objetos escrevendo firmwares em c. Depois dessa eu parei por aqui.

Entendi sim Ataxexe. Concordo com você em tudo o que disse. Eu só estou questionando o tópico “Prefira x a y” porque eu não exergo vantagem nesse tipo de abordagem. Sem boas práticas alguém quebra qualquer sistema a longo prazo usando ou não usando herança. Esses são os argumentos que defendo. Posso mudar de idéia se aparecer um argumento claro sobre esse tema até porque eu não sou dono da verdade. É só uma opinião minha que estou expondo aqui pra ter debate.

J

YvGa:
juliocbq:
YvGa:
juliocbq:

Herdar um quadrado de um retangulo seria a coisa mais esquisita que já ouvi. Afinal quem deriva de quadrado é um retangulo. Esses erros de interpretação ao meu ver são no mínimo bizonhos com o perdão da palavra. É como querer derivar uma ave de uma galinha.

Isso é questão de bom senso. Se uma pessoa não consegue enxergar uma coisa dessas não tem jeito mesmo. Nem o “Maker” resolve.

Essa questão de herança não tem nenhum mistério ou complicação. Se voce tem em mente um modelo onde as hierarquias não são profundas(trocentos níveis), é sólida(tem o comportamento garantido por interfaces) e eficiente(não herda madeira de papel) ela será mais eficiente que a composição por deixar o código simples e enxuto.

*edit aqui - Um quadrado não é um retangulo.

Herdar um quadrado de um retangulo é bisonho? Um quadrado não é um retangulo? Um retangulo deriva de um quadrado? Então você está me dizendo que um retangulo é antes de tudo um quadrado?

Você está redondamente (com o perdão do trocadilho) errado. Um quadrado é um retangulo, e a herança seria perfeitamente compreensível não fossem os poréns envolvidos.

O que eu estou dizendo, e como o seu post comprovou, as coisas não são tão cristalinas como vocês querem fazer pensar que são.

O quadrado não é um retângulo nunca. Um quadrado possui os atributos de largura e altura fixos enquanto um retangulo adiciona um comportamento variável dessas propriedades. Sua modelagem aí é que está quadrada, desculpe.

Todo quadrado é também um retângulo e um losango.

http://pt.wikipedia.org/wiki/Quadrado

Não é YvGa. Leia lá em cima o pequeno artigo. Assim você quebra qualquer sistema usando herança mesmo.

Wikipedia:
O princípio da substituição de Liskov tem um relacionamento próximo com a metodologia do projeto por contrato e coloca restrições na forma como os contratos interagem com o conceito de herança:
precondições não podem ser reforçadas em uma sub-classe. Isto significa que não é permitida uma sub-classe com precondições mais fortes que a sua super-classe.
condições posteriores não podem ser enfraquecidas em uma sub-classe. Isto significa que não é permitida uma sub-classe que contém condições posteriores mais fracas que a super-classe.
Em adição, o princípio implica que os métodos das sub-classes não podem lançar exceções não lançadas pela super-classe, exceto quando estas exceções são subtipos das exceções lançadas pelos métodos da super-classe.

Y

Julio, o artigo só corrobora o que eu disse. Um quadrado é um retângulo por definição geométrica, ou seja, no domínio, mas não pode ser tratado dessa forma. Herdar o quadrado de um retangulo não é como herdar ave de galinha, pois por definição quadrado é retangulo.

Eu repito, não estou dizendo que não se deve usar herança e concordo com você que se deve estudar o conceito, o que eu discordo é que as coisas simples e que algumas dicas para os iniciantes não devam ser levadas em conta.

J

YvGa:
juliocbq:

Não é YvGa. Leia lá em cima o pequeno artigo. Assim você quebra qualquer sistema usando herança mesmo.

Julio, o artigo só corrobora o que eu disse. Um quadrado é um retângulo por definição geométrica, ou seja, no domínio, mas não pode ser tratado dessa forma. Herdar o quadrado de um retangulo não é como herdar ave de galinha, pois por definição quadrado é retangulo.

Eu repito, não estou dizendo que não se deve usar herança e concordo com você que se deve estudar o conceito, o que eu discordo é que as coisas simples e que algumas dicas para os iniciantes não devam ser levadas em conta.

Entendi agora como você postou lá. Entendi que estava defendendo que o quadrado era um retângulo em um modelo. Desculpe.

Um quadrado não é um retângulo se você for aplicar Liskov no seu modelo e for trabalhar com herança. Toda regra tem sua exceção, mas eu gosto de aplicar essa referência nos meus projetos.

Me diz uma coisa:

se eu somar todas as cores básicas consigo preto ou branco?

D

YvGa:
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).

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.


Quadrado nunca será um retangulo… Quadrado é quadrado e retangulo retangulo… é como comparar um elipsóide com uma esfera, são duas figuras geométricas completamente diferentes. Por isso dois nomes diferentes… nunca ouvi ninguem se referir ao quadrado como retangulo, como por exemplo já ouvi se referirem ao celta como carro… Sacou???

D

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).

Cara li o post e não sei como vocês não entendem… Tá maneiro a parada, mas o problema é justamente esse Juquinha usou a herança pelo motivo e de forma errada… e mais, os gestores deveriam ter olhado a api do Juquinha, visto que ele estava estudando.

Então, eu continuo com minha opinião e acredito que vocês só não querem dar o braço a torcer, o problema não é a herança, o problema é a falta de estudo e entendimento…

É praticamente culpar a mulher que anda de mini-saia por ser estuprada… Não tem lógica…

R

diogogama:
YvGa:
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).

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.


Quadrado nunca será um retangulo… Quadrado é quadrado e retangulo retangulo… é como comparar um elipsóide com uma esfera, são duas figuras geométricas completamente diferentes. Por isso dois nomes diferentes… nunca ouvi ninguem se referir ao quadrado como retangulo, como por exemplo já ouvi se referirem ao celta como carro… Sacou???

Do ponto de vista matemático um quadrado é um retângulo sim, mas não o contrário…

Quadrado é um retângulo, porém com todos os lados iguais. Com o mínimo necessário pra se construir um retângulo você consegue fazer um quadrado, mas não o contrário…

D

Ruttmann:

Do ponto de vista matemático um quadrado é um retângulo sim, mas não o contrário…

Quadrado é um retângulo, porém com todos os lados iguais. Com o mínimo necessário pra se construir um retângulo você consegue fazer um quadrado, mas não o contrário…

Cara eu quis dizer em propriedades…

Mais uma vez volto a dizer o problema não é uso de herança… é falta de estudo…

Criado 27 de agosto de 2013
Ultima resposta 30 de ago. de 2013
Respostas 95
Participantes 12