quinta-feira, 19 de setembro de 2013

Desconstruindo a orientação a objetos: Encapsulando de verdade os seus atributos

Existe uma lei que rege todos os lugares do mundo, em todos os planos e dimensões. Esta lei é a Lei de Murphy, dizendo que você pode morrer e virar um superpolicial robô chamado Robocop. Brincadeiras a parte, a Lei de Murphy diz basicamente que se algo pode dar errado, isto irá dar errado, ou seja, você não deve deixar nenhuma fresta onde o erro pode ocorrer. O seu criador, Edward A. Murphy, um engenheiro aeroespacial, dizia que nunca devemos deixar uma brecha no projeto, pois será neste ponto onde ocorrerá a falha, e como muitas outras coisas que vieram do mundo da engenharia para a informática, esta lei também veio para o nosso ambiente.

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
Monica
Este 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