Muitos dos erros que ocorrem em sistemas orientados a objetos são causados por simplesmente a facilidade em inserir parâmetros incorretos ou ter acesso a parâmetros que não deveriam ser expostos de qualquer maneira, sem antes passar por algum tratamento ou algoritmo.
Por exemplo, observe a classe abaixo e tente observar quais erros podem ocorrer:
public class SeparadorDeAlunosPorNota { private List alunosAprovados = new ArrayList<>(); private List alunosReprovados = new ArrayList<>(); public void adicionaAluno(Aluno aluno, float nota) { if (nota > 6) { alunosAprovados.add(aluno); } else { alunosReprovados.add(aluno); } } public List getAlunosAprovados() { return alunosAprovados; } public List getAlunosReprovados() { return alunosReprovados; } }Como vocês podem ver, essa classe segue alguns princípios básicos de encapsulamento, onde seu atributos são todos privados e o acesso é feito apenas através de métodos. Porém, observando com um pouco de cuidado, podemos notar que não é bem isso que ocorre, observe abaixo um exemplo de utilização da classe acima:
public class MinhaAplicacaoEscolarMain { public static void main(String args[]) { SeparadorDeAlunosPorNota separador = new SeparadorDeAlunosPorNota(); Aluno aluno1 = new Aluno("Magali"); Aluno aluno2 = new Aluno("Cascao"); Aluno aluno3 = new Aluno("Monica"); separador.adicionaAluno(aluno1, 7.0); separador.adicionaAluno(aluno2, 4.5); separador.adicionaAluno(aluno3, 9.5); List<Aluno> alunosAprovados = separador.getAlunosAprovados(); List<Aluno> alunosReprovados = separador.getAlunosReprovados(); System.out.println("Lista de alunos reprovados: "); for (Aluno aluno : alunosReprovados) { System.out.println(aluno.getNome()); } System.out.println("Lista de alunos aprovados: "); for (Aluno aluno : alunosAprovados) { System.out.println(aluno.getNome()); } //Até aqui tudo bem, mas se atente ao que ocorre abaixo alunosAprovados.add(new Aluno("Cebolinha")); //Agora recuperando novamente a lista de alunos aprovados alunosAprovados = separador.getAlunosAprovados(); //Imprimindo mais uma vez os alunos aprovados System.out.println("Lista de alunos aprovados: "); for (Aluno aluno : alunosAprovados) { System.out.println(aluno.getNome()); } //Imprimiu o Cebolinha, que entrou na lista mesmo sem passar por uma avaliação //Desta vez o plano infalível dele funcionou } }Saida:
Lista de alunos reprovados: Cascao Lista de alunos aprovados: Magali Monica Lista de alunos aprovados: Magali Monica Cebolinha
O erro ocorre ao solicitar novamente a lista de alunos aprovados, temos agora um novo aluno, porém que não passou pela avaliação do método adicionaAluno, causando um pequeno transtorno. No contexto desta pequena aplicação já podemos ver que é um erro muito visível, porém pode ser muito pior em uma aplicação distribuída e/ou acessada por diversos clientes ao mesmo tempo, por exemplo, um website.
Apesar de todas as preocupações com a visibilidade dos campos, deixamos passar um meio de acessar diretamente um dos atributos, através de sua referência em memória. O método getAlunosAprovados devolve esta referência quando é invocando, deixando exposto uma parte do nosso objeto. Para tratar este problema, bastaria realizar uma alteração simples, criando uma cópia da lista e devolvendo para o cliente, conforme abaixo:
public class SeparadorDeAlunosPorNota { //...métodos e atributos anteriores public List getAlunosAprovados() { return new ArrayList<Aluno>(alunosAprovados); //Utilizando o construtor de cópia de ArrayList } public List getAlunosReprovados() { return new ArrayList<Aluno>(alunosReprovados); } }E a saida agora é:
Cascao Lista de alunos aprovados: Magali Monica Lista de alunos aprovados: Magali MonicaEste exemplo mostra como o encapsulamento vai muito além dos modificadores de acesso. Precisamos claramente, deixar encapsulado também todas as referências aos atributos internos, não adiantando criar métodos que apenas repassem as referências internas dos objetos. Apenas em uma situação realizar esta ação não prejudicará o estado do seu objeto, quando o atributo é final e o objeto é imutável, pois desta forma não há como alterar o objeto referenciado, como no exemplo abaixo:
public class Aluno { private final String nome; Aluno(String nome) { this.nome = nome; } public String getNome() { return this.nome; } }Da forma como está em cima, o atributo nome poderia até ser publico e mesmo assim não seria possível alterar o seu valor, pois a classe String é imutável e nenhum de seus métodos alterar o seu estado, o que não é o caso, por exemplo, da classe StringBuffer, que faz o contraponto através de seu método append por questões de desempenho.
Como você viu acima, classes imutáveis auxiliam no encapsulamento dos nossos objetos, pois como o estado do objeto não se altera, não existe como inserir informações inconsistentes dentro do objeto, sendo que o máximo que pode ocorrer é a criação de um novo objeto com um estado não coerente. No próximo post vamos falar um pouco mais sobre estes objetos, como construir as suas classes e quais os benefícios.
Nenhum comentário:
Postar um comentário