14 de jul. de 2011

Enumerados em Java

Intro

Muitas pessoas não utilizam os tipos enumerados em Java, seja por não conhecerem por estar começando a programar em Java ou, no caso dos experientes, serem relutantes a mudanças (enums foram introduzidos no Java 5).

O fato é que enums quebram um galhão e, diferente como alguns pensam, não são apenas constantes (como é no C# por exemplo), eles podem ter atributos e métodos.

Para entender a utilidade de enums vamos aplicá-los em dois estudos de caso:
1: Uma refatoração de "obsessão primitiva" (um mau cheiro)
2: Introdução do Design Pattern Strategy (Padrão de Projeto Estratégia)

Neste post vou abordar o uso de enums em refatorações e no próximo post abordo o segundo caso, ok?



Usando enums em refatorações

Imagine a seguinte implementação:

public class Usuario {

    String nome;
    String login;
    String senha;
    int    perfil = 0;

}

Não fica claro o que perfil quer dizer, fica?

Então podemos adicionar um comentário! (a.k.a. desodorante!)

public class Usuario {

    String nome;
    String login;
    String senha;
    
    // perfil do usuário pode ser:
    // 0:visitante,
    // 1:usuário,
    // 2:funcionário,
    // 3:gerente,
    // 4:administrador
    
    int    perfil = 0;

}

Há ainda os que colocam caracteres de destaque no comentário (péssimo amigo):

// --------------------------------------------------------- //
// perfil do usuário pode ser:                               //
// 0:visitante,                                              //
// 1:usuário,                                                //
// 2:funcionário,                                            //
// 3:gerente,                                                //
// 4:administrador                                           //
// --------------------------------------------------------- //
    
int    perfil = 0;

Steve McConnel, respeitado Engenheiro de Software diz:
Bom código é sua melhor documentação. Sempre que você estiver por adicionar um comentário, pergunte a si mesmo: 'Como eu posso melhorar o código para que este comentário não seja necessário?'

Antes do Java 5 a opção era usar constantes, algo como:

public class Usuario {

    String nome;
    String login;
    String senha;
    int perfil = Perfil.VISITANTE;

    public abstract class Perfil {

        public static final int VISITANTE = 0;
        public static final int USUARIO = 1;
        public static final int FUNCIONARIO = 2;
        public static final int GERENTE = 3;
        public static final int ADMINISTRADOR = 4;
    }
}

O que já é bem mais profissional mesmo que não ofereça a segurança do enum. Digo isto por causa do benefício da tipagem forte, o que faz o Java uma opção para softwares robustos. Usando a implementação com constantes não há impedimento que alguém faça:

Usuario umUsuario = new Usuario();
umUsuario.perfil = 8;

Afinal, a variável perfil é do tipo inteiro. Claro que se encapsularmos a configuração do perfil em um setter (setPerfil(int perfil)) podemos tratar o número para que seja válido. Mas vamos um passo além, usaremos enums, como abaixo:

public class Usuario {

    String nome;
    String login;
    String senha;
    Perfil perfil = Perfil.VISITANTE;

    public enum Perfil {

        VISITANTE, USUARIO,
        FUNCIONARIO, GERENTE,
        ADMINISTRADOR;
    }
}

Agora, a variável perfil é do tipo Perfil. Apenas os valores enumerados são aceitos, além de nulo é claro, pois o enumerado é um tipo (classe).

Sempre é preferível implementar utilizando a tipagem forte, que é bem viva no Java. A tipagem forte permite ver possíveis erros ainda em tempo de compilação e benefícia o código coletivo já que a intenção é declarada e a quantidade de valores aceitos é limitada. Seria diferente se usássemos Strings por exemplo, que é aberto a qualquer coisa.

Os níveis numéricos ainda podem ser obtidos, como no exemplo:

Usuario umUsuario = new Usuario();
umUsuario.perfil = Usuario.Perfil.GERENTE;

// mostra o índice
System.out.println(umUsuario.perfil.ordinal());
// mostra o nome
System.out.println(umUsuario.perfil.toString());

Simples não?

Outro caso comum é na passagem de parâmetros. Imagine um funcionário que é recuperado de um banco de dados e tem seu salário calculado assim:

Funcionario func = ds.findFuncionarioById(1288);

double salario = func.calculaSalario(3, true);

O que é 3? E o true? É para calcular de verdade? hehehe

Bem, teríamos que ver a assinatura do método para entender. Digamos que seja isto:

public double calculaSalario(int mes, boolean liquido) {
    // implementação
}

Ah! O número é o mês e o true é para calcular liquido. False calcula o quê? Deve ser o salário bruto, eu acho.

Não preciso dizer que em programação não há espaço para achismo, então vamos implementar melhor este método usando o quê? usando o quê? ENUMS!

public class Funcionario {

    public enum Mes {
        JANEIRO, FEVEREIRO, MARCO,    ABRIL,   MAIO,     JUNHO,
        JULHO,   AGOSTO,    SETEMBRO, OUTUBRO, NOVEMBRO, DEZEMBRO;
    }
    
    public enum Valor {
        LIQUIDO, BRUTO;
    }

    public double calculaSalario(Mes mes, Valor valor) {
        // implementação
        return 0.0;
    }

}

Com esta implementação agora eu posso fazer:

Funcionario func = ds.findFuncionarioById(1288);

double salario = func.calculaSalario(Mes.MARCO, Valor.LIQUIDO);

Melhor?

Bem, mas digamos que ainda queira se manter a opção de passar o mês como inteiro. Note, usar tipos primitivos permitirá que seja passado qualquer valor, ou seja, algo como calculaSalario(15), bem propenso a erros. Também é o caso de querermos o número 1 para Janeiro, 2 para Fevereiro, etc, o que não é o caso quando chamamos ordinal() que começa com zero.

Então vamos preparar nosso enum para ser instanciado a partir de um inteiro para que faça a numeração correta:

public enum Mes {

    JANEIRO(1),  FEVEREIRO(2), MARCO(3),     ABRIL(4),
    MAIO(5),     JUNHO(6),     JULHO(7),     AGOSTO(8),
    SETEMBRO(9), OUTUBRO(10),  NOVEMBRO(11), DEZEMBRO(12);
    public int Numero;

    Mes(int n) {
        this.Numero = n;
    }

    public static Mes valueOf(int index) {
        for (Mes mes : EnumSet.allOf(Mes.class)) {
            if (mes.Numero == index) {
                return mes;
            }
        }
        return null;
    }
}

O valor entre parênteses nos enumerados definem o enum passando o número no construtor. Assim sendo, se eu chamar Mes.JULHO.Numero retorna 7.

O método valueOf(int) devolve o enumerado adequado conforme o número, ou nulo se não for encontrado.

A implementação da chamada do método calculaSalario usando inteiros fica assim:

double salario = func.calculaSalario(Mes.valueOf(3), Valor.LIQUIDO);


Conclusão

Java é uma linguagem fortemente tipada e os programadores devem tirar proveito deste recurso que, naturalmente, faz do Java uma linguagem robusta, confiável e preferida por empresas de grande porte.