Enums X Constantes

Estou dando mais uma refatorada naquele meu projeto de ARS.

Antes de tentar as soluções propostas no outro tópico, fiquei curioso com a substituição de Constantes por Enum. No livro Effective Java, Joshua Bloch fala para usar Enums no lugar de Constantes inteiras. Já no livro do Fowler (Refactoring…) não vi nada que se relacionasse a esse tipo de refatoração, apenas substituição de Enums por State/Strategy, Classe ou Subclasses.

Entretanto, gostei da solução do Bloch, pois posso colocar toda a lógica que envolve as Constantes no Enum - Testes, Maps, etc.

Assim, vou entender esse primeiro passo para depois pensar nas soluções propostas por Fowler. Gostaria de saber a opnião do pessoal quanto a esse caso.

Abaixo seguem os códigos gerados. Não gostei de algumas coisas, como o método get para resolver os ordinais que não iniciam em zero. Mas em um balanço geral, a organização do código pesou para o lado dos Enums, por enquanto. A classe Classification (criada a partir das refatorações do tópico anteriormente citado) ficou muito mais simples, tendo apenas a responsabilidade de juntar os 3 tipos de classificação que possuo no sistema.

Usei os livros citados como referência, mais este site.

public enum Complexity {
	COMPLEXA(1, "Complexa"), 
	MEDIA(2, "Média"), 
	SIMPLES(3, "Simples"), 
	MUITO_SIMPLES(4, "Muito Simples");
	
	private final int key;
	private final String value;
	
	Complexity(int key, String value){
		this.key   = key;
		this.value = value;
	}
	
	public static String get(int key){
		return Complexity.values()[key - 1].value;
	}
	
	public static boolean isComplexa(int complexity){
		return complexity == COMPLEXA.key;
	}
	
	public static boolean isMedia(int complexity){
		return complexity == MEDIA.key;
	}
	
	public static boolean isSimples(int complexity){
		return complexity == SIMPLES.key;
	}
	
	public static boolean isMuitoSimples(int complexity){
		return complexity == MUITO_SIMPLES.key;
	}
}

O uso ficou assim:

public class TestCase {

	public static void main(String[] args) {
		
		int complexity = 4; 
		
		System.out.println(Complexity.get(complexity));
	}
}

Qualquer conselho será muito bem vindo.

Obrigado!

É que os enums, dessa forma, são uma implementação modificada de um padrão estrategy. A única diferença é que ela não é extensível, justamente para permitir o uso dos objetos em compile-time.

Seu exemplo também é meio infeliz. Talvez um exemplo mais realista do enum possa mostrar o poder dessa técnica. Dê uma olhada:
http://www.guj.com.br/posts/list/55885.java#293436

[quote=ViniGodoy]É que os enums, dessa forma, são uma implementação modificada de um padrão estrategy. A única diferença é que ela não é extensível, justamente para permitir o uso dos objetos em compile-time.

Seu exemplo também é meio infeliz. Talvez um exemplo mais realista do enum possa mostrar o poder dessa técnica. Dê uma olhada:
http://www.guj.com.br/posts/list/55885.java#293436[/quote]

Em primeiro lugar, obrigado.

Eu não tentei dar um exemplo e nem algo que justificasse ou não o uso de Enum. Estou procurando opção de melhoria. A classe Classification está inchada, cheia de responsabilidades e chata de entender. Quero mudar as constantes e o comportamento que envolve cada um dos tipos de Classification para um módulo separado. Primeiro pensei em Enums. Foi possível, esta fácil de entender, mas tem aquele get() feio.

Agora estou vendo como fica em uma classe. Aparentemente a classe continua com o Map e as Constantes. Mas também fica claro, pois também modulariza os atributos e comportamento.

Sei lá… estou vendo ainda…

Abraços.

EDIT: Valeu pelo link… está ajudando bastante.

No caso do seu get, eu acharia mais legal ele retornar o enum e não o value dele, algo do tipo “me traga o enum correspondente a esse inteiro”.

E nas classes que usam, não deveria usar int mais, todo lugar que faz referencia para essa constante deveria agora fazer referencia para um Complexity.

Seguindo o tópico que o ViniGodoy sugeriu e a idéia do Victor no meu outro tópico, cheguei a isto:


public enum Score {
	VERY_HIGH {
		@Override
		public Double value() {
			return 100.0;
		}
	},
	HIGH {
		@Override
		public Double value() {
			return 70.0;
		}
	},
	ABOVE {
		@Override
		public Double value() {
			return 30.5;
		}
	},
	MEDIUM {
		@Override
		public Double value() {
			return 20.0;
		}
	},
	LOW {
		@Override
		public Double value() {
			return 10.5;
		}
	},
	VERY_LOW {
		@Override
		public Double value() {
			return 7.0;
		}
	};
	
	public static Double getValue(Score score){
		return score.value();
	}
	
	public abstract Double value();
	
}

Infelizmente, não consegui eliminar totalmente os IF’s, já que a decisão de qual estratégia usar, não é definida em tempo de projeto (como no caso de contains no exemplo do Vini) e sim em tempo de execução.

Entretando, eliminei várias classes, pois a estratégia de pontuação ficou no Enum.

Segue o IF que sobrou:

public class Classification {
	
	private Status status    = Status.NOT_DEFINED;
	private String detStatus = "";
	private Complexity complexity = Complexity.NOT_DEFINED;
	private Occurrence occurrence = Occurrence.NOT_DEFINED;
	
	private boolean unfounded = false;
	
	public Double getScore() {
		if (isVeryHigh()) {
			return Score.getValue(Score.VERY_HIGH);
			
		}else if (isHigh()){
			return Score.getValue(Score.HIGH);
			
		}else if (isAbove()) {
			return Score.getValue(Score.ABOVE);
		
		}else if (isMedium()){
			return Score.getValue(Score.MEDIUM); 
			
		}else if (isLow()){
			return Score.getValue(Score.LOW);
			
		}else if (isVeryLow()){
			return Score.getValue(Score.VERY_LOW);
			
		}else {
			return 0.0;
		}
	}
}

Pelo que podem perceber já substitui, também, a referência às constantes pelo Enum.

Continuo querendo evoluir nesse código. Agradeço a todos que já sugeriram alguma coisa.

Obrigado!

Eu mudaria algumas coisas.

Isso aqui:

return Score.getValue(Score.MEDIUM);

Pode ser transformado nisso aqui:

return Score.MEDIUM.value();

E eliminaria getValue estatico de Score.

Depois acho que getScore deveria retornar o enum Score e ai os metodos isXXX faria algo como:

	public boolean isVeryHigh() {
		return getScore() == Score.VERY_HIGH;
	}

Os metodos chamadores de getScore seriam refatorados, ou entao criaria um getScoreValue o que retorna o getScore().value(). Se não quiser mudar nenhuma outra classe, ai ao inves de fazer isso, criaria um getScoreEnum() e ao inves de retornar um Score em getScore(), continua retornando um valor double (getScoreEnum().value()).

Ah, e Enum vc pode definir em tempo de execução tb.

Posta como eh seus metodos isXXX que ai da para postar uma alteração mais completa.

luBS, obrigado pelo interesse!

O problema é que a decisão sobre qual estratégia será usada só será tomada em runtime.

Por isso, acho que pelo menos um IF vai sobrar. No exemplo do Vini, em tempo de projeto, ele já sabia qual estratégia usar (no caso o CONTAINS).

Agora a questão do: Enum.ENUMERADO.metodo():

Cara… isso me doeu os olhos na primeira vez que implementei, não sei se é a melhor prática. Vocês é que estão me mostrando o caminho correto com relação aos Enums. Assim, quando vi o exemplo do Godoy, achei mais limpo e não me doeu as vistas. =)

Mas, sou um mutante com relação ao que estou aprendendo, mudo de opnião rapidamente. Se for apenas questão de costume, me acostumarei com a essa forma proposta por você. =)

Quanto ao código dos isXXX(), postarei quando eu chegar em casa.

Obrigado!

Abraços!