GUJ Discussões   :   últimos tópicos   |   categorias   |   GUJ Respostas

Herança

boa noite pessoal…

estou estudando java e quero a opinião de alguém experiênte,

quero saber quem é contra ou a favor do uso da herança na programação java…

opinem

obrigado

Java é uma linguagem de programação orientada a objetos o que significa que vc deve seguir as boas práticas de orientação a objetos o que por sua vez significa que sempre que necessário vc deve utilizar seus conceitos:Herança, Encapsulamento e Polimorfismo. Basicamente é nisso que consiste um programa orientado a objetos, se vc não usar esses três conceitos estara pensando de modo estruturado e não foi para isso que Java foi desenvolvido. A programação orientada a objetos facilita a manutenção do código e tb permite sua reutilização economizando o tempo do programador e como todo mundo sabe tempo é dinheiro.

A orientação a objetos nos oferece muitos recursos interessantes como o OliveirakunJava disse,porem a Herança as vezes quebra o encapsulamento.A ideia de economizar codigo herdando da classe mae pode parecer legal no começo, mas no futuro podemos nos deparar com casos onde ‘é um’ não existe.

Existe uma discussão legal no blog da Caelum sobre o mal uso da Herança
http://blog.caelum.com.br/2006/10/

espero ter ajudado

Eu geralmente prefiro composição ao invés de herança. Deixa o código menos acoplado e mais fácil de manter.

Raafer, não entendi direito o que você quis dizer, mas acho que herança não mata encapsulamento não. Se uma classe ou método é declarada como não estática, private, por exemplo, a herança não quebrará esse encapsulamento, sendo necessário a criação de objeto para acessar tal método.

Pode ser que eu disse bobagem, pois estou começando a aprender orientação a objetos em Java, por isso gostaria que dessem-me om feed back de minha explanação. Aqui no tópico temos um expert em java, o Vinigodoy, que pode responder essa minha dúvida.

Bem, respondendo a pergutna do criador do tópico, acho que essa pergunta não se aplica, pois Java é puramente orientdo a objeto, mesmo que esteja programando de forma estruturada você estará criando objetos, por exemplo. um bom exemplo disso é quando, numa interface gráfica, você queira usar um botão, deve ser crido um objeto do tipo Button com a declaração JButton botao = new JButton().

Acho que é isso !

[quote=Valder Olmo Corrêa]Raafer, não entendi direito o que você quis dizer, mas acho que herança não mata encapsulamento não. Se uma classe ou método é declarada como não estática, private, por exemplo, a herança não quebrará esse encapsulamento, sendo necessário a criação de objeto para acessar tal método.

Pode ser que eu disse bobagem, pois estou começando a aprender orientação a objetos em Java, por isso gostaria que dessem-me om feed back de minha explanação. Aqui no tópico temos um expert em java, o Vinigodoy, que pode responder essa minha dúvida.

Bem, respondendo a pergutna do criador do tópico, acho que essa pergunta não se aplica, pois Java é puramente orientdo a objeto, mesmo que esteja programando de forma estruturada você estará criando objetos, por exemplo. um bom exemplo disso é quando, numa interface gráfica, você queira usar um botão, deve ser crido um objeto do tipo Button com a declaração JButton botao = new JButton().

Acho que é isso ![/quote]

Isso mesmo Valder, o usuário só quebra o encapsulamento quando você declara classes ou métodos estáticos. Complementando o assunto, mesmo que essa classe não seja estática, um simples atributo declarado como public pode ser exposto na classe filha. Aí sim voce quebra o encapsulamento.

Encapsulamento significa não precisar saber como uma classe funciona por dentro para poder utilizá-la. Você só as usa, e ponto.

Com herança, o desenvolvedor da superclasse tem que assegurar que as subclasses não quebrem quebrem o seu comportamento, e que os desenvolvedores da subclasse sigam o comportamento esperado dos métodos sobrescritos. Um tem que saber como o outro funciona, e isso leva à um acoplamento muito forte entre ambas as classes.

Experimente mudar algo superclasse para ver a quebradeira que acontece.

De onde surgiu que métodos estáticos quebram o encapsulamento?

Eles são tão encapsulados quanto os não estáticos. O que difere um método estático de um não estático é o fato do método atuar sem a necessidade de uma instância. Ou seja, um método estático não é exatamente parte da OO, se falarmos de um ponto de vista mais purista.

Existem 3 tipos de encapsulamento, e é importante reforçarmos todos eles:

  1. Encapsulamento do tipo de dado;
  2. Encapsulamento da lógica de negócio;
  3. Encapsulamento da classe;

O primeiro é o que o pessoal conhece mais. Dado um atributo, torne-o private e coloque gets e sets:

public class Semaforo {
     public enum Color { VERMELHO, VERDE, AMARELO };
     private int cor = Color.RED.getRgb();

     public int getCor() { return cor; }
     public void setCor(Color cor) { this.cor = cor.getRgb(); }
}[/code]

O código acima é um exemplo do primeiro caso. Nosso usuário não sabe exatamente qual tipo de dado foi usado para representar o atributo cor. O que ele sabe é que ele fornece um Color na entrada e obtém um int na saída. Mas nada garante a ele, com 100% de certeza, que existe um atributo do tipo Color dentro da classe (e de fato, na classe acima não existe). Assim, com esse tipo de encapsulamento, poderíamos até mudar o tipo interno, sem afetar os demais usuários do programa:

[code]
//Segunda versão da classe.
//Os usuários dessa classe não foram afetados pela mudança.
public class Semaforo {
     private Color cor = Color.VERMELHO;

     public int getCor() { return cor.getRgb(); }
     public void setCor(Color cor) { this.cor = cor; }
}[/code]


A presença do get e set garantem encapsulamento? Da representação física, até que sim. Mudamos o int ali de dentro por um Color, sem que os usuários da classe externa vissem, bastando que mantivessemos os método gets e sets ainda usando a cor.

Agora a classe, desse jeito, permite que vc faça trocas de cores absurdas. Como do vermelho para o amarelo e depois para o vermelho novamente. Ou, permite até que nunca passe pelo amarelo. Novamente, a parte física do atributo está encapsulada, mas sua lógica não. 

Você poderia então validar a classe, usando ifs e jogando exceções, certo? O novo setCor ficaria assim:
[code]public void setCor(Color cor) { 
    if (this.cor == Color.VERMELHO && cor != Color.VERDE ||
        this.cor = Color.AMARELO && cor != Color.VERMELHO ||
        this.cor == Color.VERDE && color != Color.AMARELO)
       throw new IllegalArgumentException("Troca de cor inválida!");

    this.cor = cor; 
}
[/code]

Vamos supor que duas funções, em classes distintas, feitas por programadores diferentes, tentem usar essa classe. O que obteríamos são duas funções assim:

[code]//Função na classe rodovia
public void atualizaSemaforo() {
    if (semaforo.getColor() == Semaforo.Color.VERMELHO) 
        semaforo.setColor(Semaforo. Color.VERDE);
    else if (semaforo.getColor() == Semaforo. Color.AMARELO) 
        semaforo.setColor(Semaforo. Color.VERMELHO);
    else if (semaforo.getColor() == Semaforo. Color.VERDE) 
        semaforo.setColor(Semaforo. Color.AMARELO);
}

//Função na classe auto-estrada, feita por outro programador
public void atualizaSemaforo() {
    switch (semaforo.getColor()) {
        case Semaforo.Color.VERMELHO:
            semaforo.setColor(Semaforo. Color.VERDE);
            break;
        case Semaforo.Color.AMARELO:
            semaforo.setColor(Semaforo. Color.VERMELHO);
            break;
        case Semaforo.Color.VERDE:
            semaforo.setColor(Semaforo. Color.AMARELO);
            break;
    }
}

Notaram que, apesar de diferentes, os dois métodos fazem exatamente a mesma coisa?! Agora, pensem numa situação diferente. Que o governo decidiu que, após o verde, o sinal deve passar também para o amarelo antes de ir para o vermelho. Você altera os sets do seu semáforo para lançar a nova exceção. Mas, o que acontece com aqueles dois códigos? Quebram.

O problema aqui é que, embora a resentação física do atributo esteja realmente validada, a lógica de negócio, sobre como o semáforo troca suas cores, ainda é publica. Ou seja, o usuário da classe ainda tem que tomar conhecimento de “como a troca ocorre” para poder usar a classe.

O correto mesmo seria eliminar o método setter e trocar para um método chamado trocaCor(). Esse método trocaria cores sempre na ordem correta. O que acontece com esse código?

public class Semaforo {
     public enum Color { VERMELHO, VERDE, AMARELO };
     private int cor = Color.RED.getRgb();

     public int getCor() { return new Color(cor); }

     public void trocaCor() { 
          if (cor ==Color.VERMELHO) 
               cor = Color.VERDE;
          else if (cor == Color.AMARELO) 
               cor = Color.VERMELHO;
          else if (cor = Color.VERDE) 
               cor = Color.AMARELO;
     }
}

Agora, nossos programadores não terão que fazer mais ifs, ou switchs para trocar a cor do nosso semáforo. Podemos até alterar a lógica por trás dessas trocas, sem causar uma quebradeira geral.

Finalmente, o terceiro tipo de encapsulamento ocorre em classes formadas pro relação todo-parte. Ou seja, quando há composição. Um objeto pode conter uma estrutura muito mais complexa e de outros objetos dentro dele.

As vezes violamos esse regra porque, como cada objeto interno também valida suas próprias regras de negócio e é encapsulado por si só, acabamos tornando esse objeto público (muitas vezes por comodidade).

Um caso típico é numa classe de Lista encadeada, o método getFirst() ser implementado assim:

public Node getFirst() { return head; }

Ou seja, nosso usuário faria isso:

System.out.println("O valor do sapato é:" + shoesList.getFirst().getValue());

Ok, mas para o usuário da classe lista, o que interessa é sempre o valor, não o Node. Portanto, apesar do nó ser encapsulado e, mesmo que para o nosso usuário ele só permite a chamada a getValor(), estamos violando o encapsulamento de nossa classe a partir do momento em que deixamos ele conhecer a sua estrutura da lista, e não só sua funcionalidade. Assim, se um dia você quiser alterar a estrutura interna de sua lista para não-encadeada, você não conseguirá.

Esse exemplo é trivial, e dificilmente alguém gostaria de desencapsular um Node. Mas outros objetos internos, de lógicas mais complexas, as vezes são desencapsulados dessa forma.

Um cuidado especial deve ser tomado nos gets de objetos mutáveis. É muito comum vermos esse erro com classes envolvendo listas. O sujeito põe um set e um get assim:

[code]public UmaClasse {
List listaDeNaturais = new ArrayList();

public void getListaDeNaturais() {
return listaDeNaturais;
}
public void addNatural(int valor) {
if (valor < 0)
throw new IllegalArgumentException(“Valor < 0!”);
listaDeNaturais.add(valor);
}
}[/code]

Tudo funciona nesse código, certo? Errado. O get promove a violação do encapsulamento, já que alguém poderia fazer isso:

UmaClasse umObjeto = new UmaClasse();
umObjeto.getListaDeNaturais().add(-10);

Há duas formas de corrigir o problema:

  1. Você deve fazer uma cópia do objeto, antes de retorna-lo.
  2. Usar um wrapper, que elimina os sets. É o que se faz no caso das lists e demais collections:

Correto 1: Fazer uma cópia da lista:

[code]public UmaClasse {
List listaDeNaturais = new ArrayList();

public void getListaDeNaturais() {
return new ArrayList(listaDeNaturais);
}
public void addNatural(int valor) {
if (valor < 0)
throw new IllegalArgumentException(“Valor < 0!”);
listaDeNaturais.add(valor);
}[/code]

Correto 2: Usar um wrapper de imutabilidade:

[code]public UmaClasse {
List listaDeNaturais = new ArrayList();

public void getListaDeNaturais() {
//Retorna um wrapper, que é uma versão imodificável da lista
return Collections.unmodifiableList(listaDeNaturais);
}
public void addNatural(int valor) {
if (valor < 0)
throw new IllegalArgumentException(“Valor < 0!”);
listaDeNaturais.add(valor);
}[/code]

Como escrever um wrapper de imutabilidade? Se você tiver uma interface, fica fácil. Considere:

[code]public interface UmaInterface {
public int umGetter();
publit void umSetter(int valor);
}

public class UmaClasse implements UmaInterface {
private int valor;
public int umGetter() { return valor; }
public void umSetter(int valor) { this.valor = valor; }
}

public OutraClasse {
private UmaClasse umValor;

//O segredo é retornar pela interface
public UmaInterface getValor() { 
    //Ainda não funciona! Quebra o encapsulameto
    return umValor;
}

}
[/code]

Ok, toda essa bagunça mostra o problema. É possível acessar umValor diretamente, já que retornamos a referência ao objeto. Por sorte, o programador usou a interface num get. Então podemos escrever um wrapper:

public UmaClasseImutavel implements UmaInterface
{
     private UmaInterface valor;

     public UmaClasseImutavel(UmaInterface valor) {
        this.valor = valor;
     }

     //Delegamos o getValor() para quem estamos protegendo.
     public void getValor() { return valor.getValor(); }
     public void setValor(int valor) {
         throw new UnsupportedOperationException("Sorry, no cake for you!");
     }  
}

Agora podemos corrigir o nosso método ali em cima:

[code]
public OutraClasse {
private UmaClasse umValor;

//Agora será imutável
public UmaInterface getValor() { 
    return new UmaClasseImutavel(umValor);
}

}[/code]

É necessário proteger todos os setters dessa forma, e delegar os demais métodos que não alteram a classe. Esse é o padrão decorator. O mesmo usado nos InputStreams e OutputStreams do Java. Mas aqui, ele é usado com o propósito de "proteger contra gravação".

A vantagem do wrapper em relação a cópia é que as classes que podem modificar o objeto ainda irão faze-lo e as classes com o wrapper verão essas modificações. Ele também ocupa menos espaço, especialmente se sua classe possui estruturas internas muito grandes, como listas ou maps.

A desvantagem é que é meio pentelho de programar. Se você tiver um bean padrão, pode tentar usar a classe de proxy dinâmico do Java para facilitar o trabalho, mas eu ainda prefiro não fazer isso, já que corre-se o risco de colocar um método setter não standard no futuro e quebrar a garantia de imutabilidade.

Notem que esse mesmo tipo de problema pode ocorrer com os sets e construtores. Por exemplo:

[code]public UmaClasse {
List listaDeNaturais = new ArrayList();

public void getListaDeNaturais() {
//Retorna um wrapper, que é uma versão imodificável da lista
return Collections.unmodifiableList(listaDeNaturais);
}
public void addNatural(int valor) {
if (valor < 0)
throw new IllegalArgumentException(“Valor < 0!”);
listaDeNaturais.add(valor);
}

public void setListaDeNaturais(List lista) {
for (int valor : lista) {
if (valor < 0)
throw new IllegalArgumentException(“A lista só deve conter valores naturais!”);
}

  this.listaDeNaturais = lista;

}
}[/code]

O método setListaDeNaturais está correto? Apesar de fazer a validação, ele também viola gravamente o encapsulamento. Note que uma referência de lista externa está sendo usada internamente. Portanto, é possível fazer isso aqui:

List<Integer> naturais = new Lista<Integer>();
naturais.add(2);
naturais.add(3);
naturais.add(4);

UmaClasse obj = new UmaClasse();
obj.setListaDeNaturais(naturais); //Ok, só tem naturais.
naturais.add(-10); //Alteramos a lista interna também!!!

O correto, é fazer a cópia da lista, ao invés de utiliza-la diretamente. As classes de collections fornecem construtores próprios para isso. Eu costumo a utiliza-los sempre.

Já as suas próprias classes deveriam fornecer um método de clonagem. Ok, é difícil seguirmos essa regra à risca, já que fazer um método de clone nem sempre é correto ou trivial, mas é bom que vocês estejam conscientes desse fato.

Vejam o método corrigido:

public void setListaDeNaturais(List<Integer> lista) {
   for (int valor : lista) {
      if (valor < 0)
         throw new IllegalArgumentException("A lista só deve conter valores naturais!");
   }
   this.listaDeNaturais = new ArrayList<Integer>(lista);
}  

Bom, acho que é isso que eu tinha a dizer sobre encapsulamento. Tomara que eu não tenha confundido vocês ainda mais. :shock:

Uma boa leitura adicional também é essa aqui:
http://blog.fragmental.com.br/2008/05/18/objetos-nao-sao-atributos-funcoes/

Resumindo:

  1. gets e sets encapsulam o tipo de dado e as regras para usar o dado, mas muitas vezes não encapsulam a lógica de negócio;
  2. Nem sempre o get e o set são boas opções, procure pensar de maneira crítica em sua classe;
  3. Cuidado para não expor a estrutura física de sua classe, mesmo que ela seja composta de objetos que também validam sua lógica de negócios;
  4. Cuidado com gets que retornam um objeto mutável.
  5. Cuidado com sets de objetos mutáveis.

[quote]Encapsulamento significa não precisar saber como uma classe funciona por dentro para poder utilizá-la. Você só as usa, e ponto.

Com herança, o desenvolvedor da superclasse tem que assegurar que as subclasses não quebrem quebrem o seu comportamento, e que os desenvolvedores da subclasse sigam o comportamento esperado dos métodos sobrescritos. Um tem que saber como o outro funciona, e isso leva à um acoplamento muito forte entre ambas as classes.

Experimente mudar algo superclasse para ver a quebradeira que acontece.[/quote]

Bom pessoal acho que o Bruno Laturner complementou o que eu estava tentado explicar, e fez isso melhor do que eu.Mas vale a pena da uma olhada no artigo que indiquei.Tambem tinha muitas dúvidas quando iniciei e ainda tenho, mas aqui no guj tem bastante conteudo e artigos bons sobre Herança e toda a O.O

Nossa senhora !!!
O viniGodoy acabou de dar uma aula com louvor sobre encapsulamento e orientação a objetos !

Vinigodoy, você é demais, cara !

[quote=Bird89du]boa noite pessoal…

estou estudando java e quero a opinião de alguém experiênte,

quero saber quem é contra ou a favor do uso da herança na programação java…

opinem

[/quote]

A sua frase parte do principio que uma feature de uma linguagem pode ser boa ou má.
Isso por si só é uma valorização fútil. “coisas” não têm valor moral.
O máximo que pode acontecer é que as pessoas dêem às coisas valores morais em certos contextos.
Deste ponto de vista a herança como feature é inócua.

No contexto de boas práticas de programação existem formas de usar a herança que pode ser consideradas ruins, no sentido
que vão contra os princípios de OO ou que tornam o código mais complexo, menos legivel, menos mantenivel , etc… Ou seja, no sentido em que diminui a qualidade do código.

A primeira forma de usar a herança de uma forma anti-OO é usar herança-multipla. Do ponto de vista que a herança é uma categorização das coisas em uma herarquia e força o conceito É-UM multipla herança implica que uma classe É muitas coias simultaneamente. Isso não é um problema semântico mas sim logico. Uma coisas só tem uma identidade e portanto só pode ser uma coisa - ela mesma. Mas ela pode comportar-se como inúmeras coisas. Ou seja, a coisa É-UM X porque se comporta como um X e não porque tem a identidade de um X. Isto leva ao conceito de Interface e à condenação da herança-multipla.
Afinal Java nao tem herança-multipla e existe centenas de API em Java que não a usam, e mesmo assim funcionam.
A segunda forma de usar herança de forma anti-OO é usuá-la para reaproveitar código. Isto é um erro porque o reaproveitamento não deve se explicito. Ele é o resultado de um bom design OO e não deve ser forjado à força.
Este é um assunto complexo de explicar por palavras, mas basicamente se resume a : não use herança para aproveitar codigo.
Erros históricos do uso de herança desta forma, que são condenados até hoje são as famosas heranças de Stack de Vector e Properties de Hashtable. Estas heranças estão no muro da vergonha pelo mundo afora como exemplos de má OO dentro da API Java. ( Em “defesa” podemos dizer que API 1.0 não levava padrões muito em conta)

Herança não é uma forma de polimorfismo. Herança não é um mecanismo para reaproveitar código. Herança não é um mecanismo de encapsulamento. Herança não viola o encapsulamento (são os programadores que violam conceitos não os outros conceitos)
Herança é simplesmente um mecanismo para categorizar as classes de forma hierarquica de forma a poder usar um operador É-UM entre elas. O que casa programador faz com isso é da responsabilidade dele e não podemos culpar a feature ou o java por isso.

[quote=Valder Olmo Corrêa]Nossa senhora !!!
O viniGodoy acabou de dar uma aula com louvor sobre encapsulamento e orientação a objetos !

Vinigodoy, você é demais, cara ![/quote]

Valeu, fazia tempo que eu queria escrever um pouco mais sobre esse assunto. Eu geralmente faço isso com dúvidas recorrentes e daí coloco a resposta nos meus favoritos.

Então, 70% é por eu gostar de ensinar mas outros 30% é por pura preguiça mesmo. =D

PS: Atualizei o post ali em cima, no final, falando sobre quebra do encapsulamento em sets também. Outro erro muito comum.

Boa tarde pessoal,

li todo o tópico, visitei os links indicados e os li. Entretanto, ainda tenho uma dúvida (mesmo com as respostas técnicas do ViniGodoy :smiley: ), eu ainda tenho uma pequena dúvida e gostaria de entender (se possível com um exemplo bem simples) para depois eu ler o tópico de novo e entender melhor. Então minha dúvida é: Para que encapsular códigos? …porque as pessoas sabem que um carro anda, porém não precisam saber como funciona a mecânica envolvida no motor… beleza :smiley: consigo entender isto. Mas, voltando a informática, mesmo que outro programador não consiga alterar seus get e set na classe que ele criou, ele não pode ir no seu código fonte e alterar o código pessoalmente, mas para que ele iria fazer isto? a final ele também é um programador e não vai estragar algo que funcione (talvez ele até melhore) e por final só se você fizer um código e deixa-lo igual no NetBeans (quando você clica em uma classe com a tecla Ctrl pressionada , você acessa a classe, entretanto, não consegue altera-lá porque está com o código pintado de cinza).

Se alguém tiver entendido o que eu escrevir por favor me responda, caso contrário eu tento explicar de novo. :slight_smile: Obrigado

  1. Boa parte do código deverá ser construído para ser reutilizado. Não necessariamente por quem o programou. No caso de APIs (como a do próprio Java), dificilmente o usuário será quem programou;
  2. Você normalmente trabalha em equipe, e é bom saber que o seu coleguinha menos experiente não vai colocar a mão no código onde ele não deve;
  3. Código encapsulado é mais simples de entender. Imagine se os objetos tivessem no “autocomplete” um monte de métodos que você nunca deveria chamar diretamente. Isso não só deixaria muito mais complicado você entender como usar aquele objeto, como também um programador mais descuidado poderia simplesmente chama-los erroneamente. Muito melhor deixa-los privados.

Sempre que analisar técnicas de desenvolvimento, pense que os programas são feitos em equipe. Há muitas práticas que realmente não fazem sentido se vc olhar o contexto de um programador solitário.

Obrigado viniGodoy :smiley: :smiley: :smiley:
Agora encapsulamento e seu código anterior fazem sentido para mim… :idea: :idea: :idea:

Valeu

Senhores

Sem querer aumentar a discussão, mas dando uma dica de quem já passou por todas essas dúvidas, aconselho a quem quiser se aprofundar sobre os assuntos de O.O, tais como Encapsulamento, Herança (ou generalização e especialização), Polimorfismo e coisas afins, estudar um pouco mais sobre os famosos Design Patterns, pois esses conceitos por si só as vezes nos deixam confusos, mas quando entendemos sua aplicação em padrões de projetos, fica mais claro de entender os famosos “por quê” que surgem.

Tenho usado isso com os meus alunos de disciplina de análise e Projeto Orientado a Objetos nas universidades por onde dou aula, e tem sido bastante produtivo.

Lembrem-se que esses conceitos devem ser aplicados bem antes da “programação”, pois são eles quem salvam a arquitetura de um projeto.

Abraços.

//