Revisitando a Orientação a Objetos: encapsulamento no Java

Façamos uma aposta. Tenho certeza que você, ao ver a classe abaixo, consegue perceber um problema nela:

class Pedido {
  public String comprador;
  public double valorTotal;
  // outros atributos
}

Sim. Os atributos estão todos públicos! Isso vai exatamente contra uma das nossas primeiras lições quando aprendemos Java: atributos devem ser privados e precisamos de getters e setters para acessá-los. Vamos então fazer essa mudança no código.

class Pedido {
  private String comprador;
  private double valorTotal;
  // outros atributos

  public String getComprador() { return comprador; }
  public void setComprador(String comprador) { this.comprador = comprador; }

  public double getValorTotal() { return valorTotal; }
  public void setValorTotal(double valorTotal) { this.valorTotal = ValorTotal; }

  // outros getters e setters
}

Agora está melhor, certo? Ainda não. Deixamos escapar na verdade o grande princípio que está por trás da ideia de colocar atributos como privados. Do jeito que a classe Pedido está nesse momento, podemos fazer coisas como:

Pedido p = new Pedido();
// muda valor do pedido para 200 reais!
p.setValorTotal(p.getValorTotal() + 200.0);

Mas aonde está o problema? Imagine outras 10 classes que fazem a mesma coisa: de alguma forma, elas manipulam o valor total do pedido.

Agora imagine que a regra de negócio do pedido mude: todo item comprado ganha desconto de 5% se o valor dele for superior a 1000 reais. Implementar essa mudança não será tarefa fácil. Precisaríamos fazê-la em diferentes classes do sistema.

Quanto tempo demoraremos para mudar o sistema? Não sabemos exatamente aonde devemos fazer as mudanças já que elas estão espalhadas pelo código. Esse, aliás, é um dos grandes problemas de códigos legados: uma simples mudança precisa ser feita em tantas classes e, na prática, sempre esquecemos algum ponto, e nosso sistema frequentemente quebra.

A classe Pedido não foi bem desenhada. Demos acesso direto ao atributo valorTotal, um atributo importante da classe. Veja que o modificador private nesse caso não adiantou de nada, já que demos também um setter para ele. Vamos tentar diminuir o acesso ao atributo, criando métodos mais claros para a operação de depósito:

class Pedido {
  private String comprador;
  private double valorTotal;
  // outros atributos

  public String getComprador() { return comprador; }
  public double getValorTotal() { return valorTotal; }

  public void adiciona(Item item) {
    if(item.getValor() < 1000) this.valorTotal += item.getValor();
    else this.valorTotal += item.getValor() * 0.95;
  }
}

Agora, para adicionarmos um item no Pedido, faremos uso desse novo comportamento:

Pedido p = new Pedido();
p.adiciona(new Item("Chuveiro Elétrico", 500.0));

Mas qual a diferença entre os dois códigos abaixo?

Item item = new Item("Super Geladeira", 1500.0);

// antiga
if (item.getValor() > 1000) {
    c1.setValorTotal(c1.getValorTotal() + item.getValor() * 0.95);
}
else {
    c1.setValorTotal(c1.getValorTotal() + item.getValor());
}

// nova
c1.adiciona(item);

Veja que na primeira linha de código, sabemos exatamente COMO funciona a adição de um novo ítem no pedido: devemos pegar o valor total e somar o valor novo com desconto de 5% se ele for maior que 1000. Já na segunda linha de código, não sabemos como esse processo funciona.

Quando sabemos O QUÊ um método faz (igual ao método adiciona, sabemos que ele adiciona um ítem no pedido, por causa do nome dele), mas não sabemos exatamente como ele faz, dizemos que esse comportamento está encapsulado!

A partir do momento que as outras classes não sabem como a classe principal faz o seu trabalho, significa que as mudanças ocorrerão apenas em um lugar! Afinal, elas estão escondidas (encapsuladas)!

Ou seja, para implementar a regra de negócios nova, bastaria mexermos em um único lugar:

  public void adiciona(Item item) {
    if (item.getValor() > 1000) this.valorTotal += item.getValor();
    else this.valorTotal += item.getValor() * 0.95;

    // nova regra de negócio aqui
  }

No fim, a real utilidade do private é esconder acesso de atributos que precisam ser acessados de maneira mais inteligente. Mas veja que de nada adianta colocar todos os atributos como private e criar getters e setters para todos eles. Deixamos o encapsulamento “vazar” do mesmo jeito.

Esconda os atributos, mas pense em comportamentos inteligentes para acessá-los. Uma ótima maneira para saber se o comportamento está encapsulado é olhar para o código que faz uso dele! Se conseguirmos dizer o que o método faz, mas sem dizer como ele faz, então podemos afirmar que o comportamento está encapsulado!

Muitas vezes deixamos esses princípios passarem. Se quiser revisitar essas e outras boas práticas de Orientação a Objetos junto com os instrutores da Caelum, há mais posts por aqui, como um específico sobre esse problema dos getters e setters, o excesso de ifs e o relacionamento bidirecional entre classes. Quer praticar tudo isso com video aulas, respostas dos instrutores e correção dos seus exercícios? Confira nosso novo curso online de boas práticas de orientação a objetos!.

39 Comentários

  1. Rodrigo Ferreira 14/06/2012 at 10:14 #

    Excelente post Mauricio!

    Bem simples de entender.
    É uma pena que ainda existam programadores java que acham que encapsulamento se resume a atributos private e métodos get/set público.

  2. Rafael Rossignol 14/06/2012 at 10:23 #

    Concordo em todos os aspectos, porém os frameworks/apis de persistência nos obrigam a implementar getters e setters pra todos os atributos que são persistidos, portanto, esse controle de encapsulamento ainda tem q estar na nossa cabeça, já que não podemos deixar de implementar o setter

  3. Anderson Souza 14/06/2012 at 10:42 #

    Muito bom artigo! Só uma dúvida, se a regra diz que todos produtos com valor maior do R$ 1.000,00 recebe desconto, o ‘if’ do código da classe Pedido não está invertido?

  4. Carlos Antônio. 14/06/2012 at 11:55 #

    Exelente post, está modo deve ser ótimo para JAVA, mas com certeza se encaixa muito bem em PHP e outras linguagens…

  5. Leonardo Nunes 14/06/2012 at 11:56 #

    Ótimo artigo!

  6. Edinei 14/06/2012 at 12:10 #

    Muito bom post !!!

    É muito comum vermos isso no dia a dia, criar o atributo gerar os getter/setter (normalmente pela IDE) sem ao menos saber se de fato esses atributos deveriam ser acessados diretamente pelo cliente. Onde na verdade métodos voltados para a lógica de negócio seriam os mais indicados.

    Acontece bastante isso também quando usamos collection em nossas classes e temos um set(Collection) e um getCollection expondo demais as estruturas internas de um objeto, além de permitir os clientes manipularem o conteúdo das coleções diretamente sem ser o “dono” dessas collections. Martin Fowler mostra isso no seu livro de Refactorings, para quem quiser ver:

    http://sourcemaking.com/refactoring/encapsulate-collection

  7. Rafael Ponte 14/06/2012 at 12:23 #

    Como sempre muito simples e didático ao escrever sobre design de software – assunto este que sempre me chamou a atenção.

    Só faltou você comentar que um código com encapsulamento bem definido pode ser facilmente testado através de testes de unidade.

    Um post que também vale muito a pena ler é o post que o Phillip Calçado (aka Shoes) escreveu em 2008, http://blog.fragmental.com.br/2008/05/18/objetos-nao-sao-atributos-funcoes/ .

    Ah, como o Anderson Souza comentou, o if() do método adiciona() está invertido.

    @RafaelRossignol
    Na verdade já faz alguns bons anos que frameworks de persistência não te obrigam a ter getters e setters, como o Hibernate por exemplo.

    Contudo, a maioria gritante dos frameworks MVC te obrigam a seguir o padrão JavaBeans, ou seja, você precisará de getters e setters.

    No mais, excelente post, Aniche

  8. jonas 14/06/2012 at 14:55 #

    Parabéns pelo post Mauricio Aniche

  9. Mauricio Aniche 14/06/2012 at 18:31 #

    Olá galera, obrigado! Fico feliz que gostaram!

    @Rafael Rossignol

    Sim, infelizmente esse é um problema sério que temos: a infra-estrutura influenciando no nosso projeto de classes. Isso não deveria acontecer.

    Como citado acima, o Hibernate hoje até que não te força tanto, mas a maioria dos frameworks MVC pedem que vc tenha getters/setters nos seus objetos. A sacada é pensar em como lidar com isso.

    Momento plin-plin: O VRaptor te permite receber os dados pelo construtor, e assim você não precisa de setters aonde não quer.

    Mas concordo em abosluto com teu ponto!

  10. Mauricio Aniche 14/06/2012 at 19:09 #

    Ah, e obrigado pessoal, o if estava invertido mesmo! Já arrumei!

    “Cadê o teste desse código, Aniche!?” 😛

  11. Felipe 14/06/2012 at 22:36 #

    Excelente, Maurício !

    Amigo, posso utilizar esse conteúdo para uma aula que vou dar ?
    Colocarei a fonte do seu site

  12. Mauricio Aniche 14/06/2012 at 22:38 #

    Oi Felipe,

    Claro que pode! Fico feliz que tennha gostado!

    Um abraço!

  13. Herbert 14/06/2012 at 22:44 #

    Sei que para bom entendedor, meia palavra basta. Mas nas primeiras linha de código existe um minúsculo erro:

    Pedido c1 = new Pedido();
    // muda valor do pedido para 200 reais!
    c.setValorTotal(c.getValorTotal() + 200.0);

    Foi criado objeto c1 e não c.

    Muito bom o artigo, parabéns mais uma vez.

  14. Mauricio Aniche 14/06/2012 at 22:45 #

    Corrigido, Herbert!

  15. Herbert 14/06/2012 at 22:54 #

    só falta mudar o c.getValorTotal() para p.getValorTotal() também. Acho que não foi a toa que meu apelido no curso FJ11 era de “compilador”.

    😉

    Abraços e parabéns novamente.

  16. Guilherme Mastria 14/06/2012 at 23:29 #

    mas não é pra isso que utilizamos a classe de negócio?
    instanciando uma DAO, convém colocar esse tipo de verificação dentro de um bean??

    abraço

  17. Mauricio Aniche 15/06/2012 at 12:07 #

    Oi Guilherme,

    Por classes que negócio, vc quer dizer aquelas camadas onde enfiamos somente as regras de negócio, conforme sugerido por aquele catálogo de padrões da Sun?

    Infelizmente esses padrões promovem más práticas de código. Separar “regras de negócio” em uma camada e “dados” em outra, é voltar a programar de maneira procedural!

    E já sabemos os problemas desse paradigma: repetição de código, manutenção em diversos pontos diferentes, pois tudo está longe, e assim por diante.

    A discussão é parecida com a dos comentários acima. A infra estrutura sempre nos empurra a fazer mau uso da OO. Devemos lutar contra isso!

    Respondi?

  18. Gilmar M. dos Santos 15/06/2012 at 13:55 #

    O post é muito bom, mas nada adianta saber disso, ser os frameworks mvc nos obrigado a implementar getters e setters.

  19. Paulo Vinícius Moreira Dutra 15/06/2012 at 15:00 #

    Excelente post. Sempre presei o uso dos bons princípios da orientação a objetos. É uma pena ainda alguns frameworks nos obrigarem a usar métodos getters e setters sem realmente ser necessário.
    Um bom sistema OO, concerteza será mais fácil de dar manutenção.

    E vamos utilizar o métodos getters e setters com moderação.

  20. Luiz 15/06/2012 at 20:58 #

    Maurício,

    Ótimo post.

  21. Luis Vasconcellos 15/06/2012 at 22:50 #

    Perfeito. A ideia é encapsular dados e expor comportamento !

  22. Antonio Cesar 16/06/2012 at 13:10 #

    Independente de frameworks e especificações que nos obrigam a utilizar “Más Práticas” um bom post e uma discussão inteligente sobre boas práticas são sempre muito bem vindas….
    Excelente post que estimula e valoriza o conhecimento bem utilizado… Parabéns….

  23. Raphael Lacerda 18/06/2012 at 12:00 #

    Galera, apesar da maioria dos frameworks nos obrigarem a implementar os setters, vale lembrar que existem alternativas

    http://www.guj.com.br/java/208491-iogi—usando-objetos-imutaveis-junto-com-o-vraptor

    Excelente post… principalmente para quebrar mitos sobre atributos privados..

    o q eu fico pé da vida é q os analista de mais alto nível dizem que o software precisa ter manutenibilidade…
    agora, como vc vai explicar para o cidadao que encapsular sem usar setter vai te ajudar nesse quesito???

    Enfim, dae o q acontece na maioria dos projetos é q fica uma história para ingles ver… o cara pede manutebilidade e o programador diz q fez um codigo totalmente manutivel seguindo os conceitos de “encapsulamento”

  24. Mauricio de Mello 18/06/2012 at 13:51 #

    A Ideia básica de um bom código é não repetir código, seja em java, c# ou linguagens não orientadas a objetos.
    Ótimo post!!

  25. Henrique S. 26/06/2012 at 12:23 #

    Excelente post!

  26. Rafael 28/06/2012 at 14:50 #

    Parabens pelo otimos post.

  27. Guilherme Mastria 03/07/2012 at 22:56 #

    …depende de empresa para empresa e principalmente dos canais de acesso que aquela DAO vai ter! Se você tiver o seu sistema simplesmente Web, ok, tudo bem não ter o BO, mas você deixa o sistema altamente acoplado, ou seja, caso o seu sistema necessite de uma integração com outro de uma empresa (que comprou a sua ou foi adquirida por) ou simplesmente seja acessado por um mobile, TV digital, Web e Desktop, e com regras distintas… como o você faria? rs E hoje isso é bem propício.

  28. Mauricio Aniche 04/07/2012 at 14:34 #

    Oi Guilherme,

    Se você ainda não consegue trafegar a própria entidade de um lado pro outro (serializando em XML, JSON, ou coisa do tipo), aí vc cria uma classe que só tem atributos e só serve unica e exclusivamente para navegar de um lado pro outro (os famosos DTOs).

    Esconda a “sujeira” de converter sua entidade em um DTO, e pronto. Mas veja que essas classes não terão regra de negócio nenhum e só servirão para transferir dados de uma camada para outra.

    Faz sentido?

  29. Hélio Moura 23/07/2012 at 15:48 #

    Muito bem colocado este seu exemplo Maurício.
    Ótimo para aulas de orientação a objetos.

    Saliento ainda que o uso de DDD (Domain Driven Design) vem crescendo justamente por permitir que boas práticas como esta possam ser aplicadas.

    O uso de DDD permite ao analista/desenvolvedor isolar de fato a camada de negócio, camada esta que é composta por classes completas, isto é, com suas propriedades e comportamentos.

    Este isolamente evita ter que se criar métodos de acesso às propriedades exigidos por certos frameworks, evitando assim, a quebra do encapsulamento.

    Venho utilizando DDD ultimamente e isto me tem remetido às origens da teoria da orientação a objetos onde o quê se procurava era apenas oque hoje chamamos de boas práticas na orientação a objetos.

    Vocês da Caelum fazem um excelente trabalho!

  30. Mauricio Aniche 23/07/2012 at 18:43 #

    Oi Hélio,

    Obrigado! E concordo com vc: DDD faz o programador relembrar da boa e velha OO! 🙂

    Um abraço,
    Mauricio

  31. Andre Alves Pinto 16/08/2012 at 10:34 #

    Valeu Maurício!

  32. Maicon 19/08/2012 at 11:02 #

    Parabéns pelo post Mauricio!! Explicou de forma simples e objetiva. abraços

  33. Clayton Passos 24/09/2013 at 16:25 #

    Legal!

    Mas e quando você está usando um framework de persistencia que te obriga a criar getters e setters publicos?

  34. Paulo Silveira 24/09/2013 at 23:24 #

    Clayton, ai nao tem muito jeito mesmo. Alias, muitas vezes quebramos orientacao a objetos por causa dos frameworks!

    Vale lembrar que pra usar JPA/Hibernate nao precisa de getters e setters, da pra usar via atributos

  35. Luiz Freneda 01/09/2014 at 10:44 #

    Simples e bem explicado 🙂

    Muitas pessoas pensam apenas em private para getters e setters porque quando aprendemos sobre encapsulamento é passado o conceito de – Information hidding -, acho que um nome melhor para o conceito seria – Implementation hidding –

    Anyway, ótimo post \o\~

  36. Andre 05/12/2014 at 09:03 #

    Show, muito bem explicado

  37. Felipe Portela 02/03/2015 at 07:13 #

    Mauricio Aniche, sou estudante e estou iniciando os estudos em programação orientada a objetos. Procurei por um bom tempo um real conçeito para Encapsulamento pois um professor já havia me dito que o mal de programador moderno é achar que o encapsulamento é feito através de set/get públicos. Parabéns pelo post, é de grande conhecimento entender bem o processo de encapsular. São em detalhes como este que um programador é mais diferençiado!

    Grato.

Deixe uma resposta