Herança é "elegante"?

Olá a todos.
Estava lendo um livro e me lembrei com uma dúvida que tenho a tempos.

É adequado usar herança? Ou é melhor usar uma composição?

Sim, eu sei que em tese o primeiro é para IS-A e o segundo para HAS-A; porém pergunto isso porque algumas pessoas já me falaram que herança deve ser evitada a todo custo, por causa do grande processamento e por ser “deselegante”. Juro, nessa eu boiei, porque me parece um conceito maravilhosamente elaborado.

É vantagem usar herança (no caso IS-A) ou é melhor evitá-la? É realmente muito dispendiosa, ou isso não se reflete em Java?

(As pessoas me afirmaram que herança “não funcionava bem” tão categoricamente que me abalaram as estruturas…)

Estou lendo um livro que sempre diz : “entre heranca e composicao prefira sempre composicao”.

Heranca tem algumas desvantagem, todas as classes ficarao dependentes da implementacao da classe pai. Com isso fica dificil usar as subclasses se o codigo da classe pai nao eh mais desejado. Se vc alterar a classe pai todas as subclasses vai ser afetadas e nem sempre eh isso que vc quer. Acho que codigos que usam e abusam de heranca estao sujeito a bugs.

Composicao, as funcionalidades das classes sao adquiridas em run-time. As classes podem obter referencias das classes ao inves de herdar as funcionalidades. Fora isso vc nao tem acoplamento entre os objetos como em heranca. O que faz o codigo muito mais facil de manutencao.

As vezes eu acho que eh inevitavel usar heranca, mas eu sigo o exemplo de alguns livros e sempre implemento composicao.

//Daniel

Desculpe-me, mas não saquei :oops:

Teria como vc passar um exemplo mais palpável? Ok que eu não tenho uma super experiência, porém estou tentando imaginar quando vc modificaria o pai e não quereria que os filhos tivessem esse comportamento.

Eu entendi a lógica, porque então vc teria que modificar o método pai, ir em todos os filhos que vc não quer que mude o comportamento e sobrescrever o método em questão. Porém isso está ainda assim VAGO pra mim.

(Vc poderia citar o livro em questão para eu dar uma super procurada?)

prefira sempre composição!!!

Pow isso eh resposta? ehehe

Eu to lendo o design patterns head-first e muita coisa na net sobre OO e design patterns. Da uma procurada no google que vc encontra.

De qualquer forma eu vou procurar um exemplo legal na net e vou postar aqui pra vc ver como funciona olhando um exemplo com codigom, talvez te ajude mais.

//Daniel

Hehehehehe Respostas “porque sim, zequinha” é sacanagem!

Poxa, muito obrigada, vc já me ajudou a beça!

Prefira composição a herança.

Na composição o acoplamento entre as duas classes é menor. O que isso significa? Que a hierarquia de ambas pode correr em paralelo. A classe que compõe pode ter filhos, com variações diferentes da mesma funcionalidade. Por exemplo, é muito fácil substituir uma LinkedList por um ArrayList nesse caso:

public class Turma { public List<Aluno> alunos = new LinkedList<Aluno>(); // resto dos métodos aqui }

Mas é muito difícil nesse caso:

public class Turma extends ArrayList<Aluno> { //resto dos métodos aqui }

Note que no segundo caso, grandes são as chances de Turma<Aluno> usar os métodos protected de ArrayList<Aluno>, equanto no primeiro caso é impossível. Dependendo do quão acoplada as duas classes estiverem essa mudança pode ter um impacto profundíssimo em seu código.

A composição também tem a vantagem de poder ser definida em Runtime, como o colega falou. No caso da composição, é possível fazer isso:

public class Turma { public List&lt;Aluno&gt; alunos = null; public Turma(int numero) { alunos = AlunosDAO.carregarTurmaPorNumero(alunos); } }

Que tipo de lista será retornado por carregarTurmaPorNumero? Não sabemos. O método pode decidir se vai retornar um ArrayList, uma coleção do Hibernate, um ArrayList encapsulado pelo Collections.unmodifiableCollection, qualquer coisa. O importante é que retorne algum tipo de list. Tente imaginar como fazer isso através do extends. Impossível, a turma seria sempre um ArrayList de alunos.

Melhor do que isso, você pode ter um arquivo de configuração que retorna uma lista real na aplicação de produção, e uma lista falsa, de testes, no JUnit.

Outra vantagem da composição ocorre quando você não quer ter acesso a todos os métodos da classe que você está compondo. Por exemplo, na classe turma ali em cima, temos o método clear(), herdado de ArrayList. Vamos supor que consideremos (mais tarde) esse método perigoso, e decidamos não te-lo no código. No caso da herança, só o que podemos fazer é sobrescrever o método e lançar um UnsupportedOperationException. A interface da classe turma fica poluída com um método que lança uma exceção, sempre que é chamado. Péssimo, não? No caso da composição, simplesmente não fazemos a implementação desse método.

Finalmente, é possível compor funcionalidades de 3 ou 4 classes separadas. O que aconteceria na herança é que essas 3 ou 4 classes provavelmente se misturariam em uma única classe, pouco coesa, difícil de manter e virtualmente impossível de ser aproveitada em algum outro lugar. Essas classes separadas, por serem menores, tem um objetivo claro e podem ser até mesmo reaproveitadas em outras hierarquias de classes, maximizando assim o reuso do código. Considere por exemplo que você queria que sua turma seja salva em um arquivo XML.

Usando herança, vc tem duas possibilidades:

  1. Fazer seu XMLHelper ser pai de ArrayList. O que nesse caso é impossível, já que herdamos de uma classe que era do Java. Isso mostra uma grave limitação da herança;
  2. Fazer XMLHelper ser filho de ArrayList, e Turma ser filho de XMLHelper. Agora pergunta-se, a classe XMLHelper deveria ser um ArrayList?? Tudo indica que não. Vários métodos vão ser inflados ali.

Com composição:

  1. Adiciona-se XMLHelper como atributo de Turma, pronto!

Note que com a composição, XMLHelper não sofreu modificação. Por se tratar de uma classe separada, a classe Aluno também poderia ter um XMLHelper associado. Imagine no primeiro caso, se optassemos pela opção #2 e quiséssemos também salvar o aluno em XML. Teríamos agora o Aluno filho de XMLHelper, que seria um ArrayList! Logo, o Aluno também se tornaria um ArrayList! Péssimo, não acha?

Os exemplo aqui são um pouco extremos. É meio óbvio que turma não é um ArrayList.

Na prática, isso ocorre mas de maneira mais sutil, pois nem sempre é possível dizer com muita precisão se algo “é ou não é” determinada coisa. Quando vc precisar reaproveitar a funcionalidade, seu código já está acoplado demais e isso se tornará difícil. O importante aqui é entender que herança cria um relacionamento forte. Quanto mais métodos protegidos o filho usar, ou quando mais funcionalidade da classe pai ele estender, mais forte se torna esse relacionamento.

Herança também tende a reduzir a coesão, ou seja, suas classes ficarem maiores e com funcionalidades que fogem de seu escopo principal. Prefira sempre um conjunto de classes pequenas, mas fácil de associar (para você efetivamente brincar de playmobil com o seu código) do que um elefantão branco multifuncional.

leia sobre design patterns que vc entendera o pq é melhor compisição do que herança… mas de fato as vezes não tem como fujir da herança… depende muito do caso… mas em grande parte das vezes é possivel usar composição… composição tem a vantagem de algo ser “acoplado” ao seu objeto quando isto não é mais desejavel é só vc substituir ou tirar este acoplamento herança não herança seu objeto é o objeto pai qualquer modificação nele sera uma modificação nos seus filhos… um exemplo bem tosco seria se tivessemos uma classe carro com atributos basicos como motor, roda, suspensão, etc… e teremos classes filhas herdando todas de carro se eu mudar o motor da classe pai todas as filhas serão obrigadas a ter o mesmo motor, o mesmo para as rodas, suspensão etc… agora se eu criar cada parte separada em uma classe distinta: motor, suspensão, rodas e depois na minha classe carro eu só acoplar estes objetos posso ter distintos tipos de motores, rodas, etc… para mais diversos tipos de carros… claro que posso herdar esta classe carro de uma superclasse carro para tal ter atributos que tem em todos os carros e que não mudam como: chassis, placa, etc… este seria um exemplo bem tosco da utilidade da composição…

Entendi a questão de “muito” acopladas, acho que consegui dimensionar o tamanho do problema com os exemplos. Eu sei que são extremos, mas consegui entender a ideia (palavra-esta-que-não-tem-mais-acento-e-vou-escrever-errado-por-anos).

E de carregar dinamicamente também é uma boa!

Obrigada, garotos!

Quanto a ser dispendiosa isso é um pouco irrelevante - a JVM faz o possível para que mesmo usar uma hierarquia muito profunda de herança não seja lento; o problema é que normalmente herança cria o “fragile subclass problem”, que é mais acentuado em C++ mas também existe em Java.

[quote=thingol][quote=CintiaDR]
É vantagem usar herança (no caso IS-A) ou é melhor evitá-la? É realmente muito dispendiosa, ou isso não se reflete em Java?
[/quote]

Quanto a ser dispendiosa isso é um pouco irrelevante - a JVM faz o possível para que mesmo usar uma hierarquia muito profunda de herança não seja lento; o problema é que normalmente herança cria o “fragile subclass problem”, que é mais acentuado em C++ mas também existe em Java.
[/quote]

Como a JVM “encherga” a herança?

Oi CintiaDR

Lei este texto aqui: http://www.dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/herancavscomposicao.htm acho que ele dá um bom exemplo.

Leia este livro sobre padrões: http://www.linuxmall.com.br/index.php?product_id=3127 é fácil de achar nas livrarias. Acho que é este que o windsofhell está lendo, é bom mas existem bastante material sobre este assunto vale a pena dar uma olhada.

Agora a idéia de que composição é melhor do que herança é o tipo de coisa que vc deveria deixar de lado, porque NA MINHA OPINIÃO elas são FERRAMENTAS e como tais hora são boas hora são ruins, vai depender da SOLUÇÃO que vc irá construir. Elas por si mesmas implementam muito bem (ao menos até o momento) os pontos, que tocam o assunto, da teória da OO. Padrões, e técnicas de análise e bom senso são coisas que nos ajudam a chegar em uma boa solução agora se iremos utilizar herança ou composição (ou não, segundo Caetano Veloso) a gente decide na hora de construir a solução.

flws

fantomas ,

nisso eu concordo com você, não dá para excluir uma ou outra solução. Porém, eu não via nenhuma razão para implementar um IS-A como uma composição ao invés de herança(veja, o código fica um pouco menos legível, inclusive). Eu estou num nível de conhecimento que conheço vários patterns, porém me falta o conhecimento de quando aplicá-los, onde realmente apresentam vantagens.

Por isso a pergunta, entende? :slight_smile:

Mas vou atrás do livro, sim!

[quote=CintiaDR]
Entendi a questão de “muito” acopladas, acho que consegui dimensionar o tamanho do problema com os exemplos. Eu sei que são extremos, mas consegui entender a ideia (palavra-esta-que-não-tem-mais-acento-e-vou-escrever-errado-por-anos).

E de carregar dinamicamente também é uma boa!

Obrigada, garotos![/quote]

Ufa, ainda bem que os exemplos ajudaram, deu um pouco de trabalho escrever aquele post. :slight_smile:
Como é uma pergunta frequente (que não tem mais trema, e também provavelmente vou escrever errado por anos), vou adicionar nos meus favoritos para linkar esse tópico no futuro.

Gente! Eu cometi uma super gafe nesse tópico!

Eu imaginei que outras pessoas tivessem postado sobre isso no GUJ, mas pesquisei por “herança composição” e “herança composite” e não havia nenhum resultado. Busquei por cima neste fórum, como não achei nada, deduzi que era uma dúvida rara!
(Nem me lembrei que certas pessoas não usam acentuação! Putz)

E me esqueci do melhor amigo! Aff. Como eu achava que a razão de não usar herança era relacionada a linguagem e não a OO, só me liguei que poderia ter algo na web, até quando o ViniGodoy falou que era uma dúvida comum. Pensei “poxa, se é comum eu fui uma porta pesquisando!”

Como o google ignora as acentuações para pesquisa, ele achou vários tópicos sobre isso. Mas de qualquer jeito, o tópico ajudou bastante com referência do livro e com exemplo mais PALPÁVEL, que era o que realmente eu precisava!

Da próxima vez, me mandem procurar no google! Não é possível, enferrujei hahahahahaha

Sorry, sorry, sorry, acho que eu devia estar com muito sono! :oops:

[quote=thingol][quote=CintiaDR]
É vantagem usar herança (no caso IS-A) ou é melhor evitá-la? É realmente muito dispendiosa, ou isso não se reflete em Java?
[/quote]

Quanto a ser dispendiosa isso é um pouco irrelevante - a JVM faz o possível para que mesmo usar uma hierarquia muito profunda de herança não seja lento; o problema é que normalmente herança cria o “fragile subclass problem”, que é mais acentuado em C++ mas também existe em Java.
[/quote]

Thingol,

O certo é “fragile subclass problem” ou “fragile base class”?

Neófito,

Acho que em C++ fica “base”, mas em Java fica “subclass”. (Nossa, que caca que eu falei - eu sempre li base class usando C++).

Abraço.

"

[quote=neófito][quote=thingol][quote=CintiaDR]
É vantagem usar herança (no caso IS-A) ou é melhor evitá-la? É realmente muito dispendiosa, ou isso não se reflete em Java?
[/quote]

Quanto a ser dispendiosa isso é um pouco irrelevante - a JVM faz o possível para que mesmo usar uma hierarquia muito profunda de herança não seja lento; o problema é que normalmente herança cria o “fragile subclass problem”, que é mais acentuado em C++ mas também existe em Java.
[/quote]

Thingol,

O certo é “fragile subclass problem” ou “fragile base class”?[/quote]

Entschuldigung - o correto é “fragile base class” = FBC.

[quote=Andre Brito]Neófito,

Acho que em C++ fica “base”, mas em Java fica “subclass”. (Nossa, que caca que eu falei - eu sempre li base class usando C++).

Abraço.[/quote]

Oi André,

Acho que isso independe de linguagem, já que “base class” seria a superclasse, da qual se herda características, e “subclass” seria o contrário, a classe que herdou.