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

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?

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…

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.

1 curtida

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

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?

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.

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

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

[quote=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)… [/quote]

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.

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.

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.

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

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.

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.

[quote=Vina][quote=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.
[/quote]
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.[/quote]

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.

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

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.

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

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.

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

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

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.

Eu tmb discordo e acho que é completamente diferente.

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

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).[/quote]

Aí está um ponto interessante. Eu ainda não consegui aprender corretamente a questão da composição.

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

Seria, por exemplo, uma classe Empresa que “tem um” Cliente? Esse cenário seria o caso de uma composição?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Abraços! :wink:

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

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

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

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

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

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

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

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

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

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

Abraços! ;)[/quote]

Concorda que vc usar uma:

[code]class Carro {
}

class Celta extends carro {
}
[/code]

é muito diferente de:

class Carro { private Celta celta; }

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