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