Remover if/else em tratamento de evento

13 respostas
Reilander

Sempre vejo (e faço) códigos de tratamentos de evento do tipo:

public void keyPressed(KeyEvent e) {
   if (e.getSource() == <componente-da-view>) {
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
         // processa evento <ENTER> para esse componente
      }
      else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
         // processa evento <ESCAPE> para esse componente
      }
   }
   else if (e.getSource() == <outro-componente-da-view>) {
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
         // processa evento <ENTER> para esse outro componente
      }
      else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
         // processa evento <ESCAPE> para esse outro componente
      }
   }
   ...
}

O que eu não consegui pensar foi numa forma de remover esses if’s. Existe alguma solução para eu reusar?

Olhei o pattern Strategy/State mas não é muito bem o caso, pois neles o target em questão já tem um estado, como em uma classe Funcionario que tem um TipoFuncionario que implementa um método abstrato getSalario(). Nesse caso seria só chamar o método getSalario() que já é calculado polimorficamente o salário de acordo com o tipo de funcionário (comum, gerente, supervisor, etc).

De qualquer sorte, queria remover essa quantidade de if’s porque todas as vezes que olho pra esse tipo de código sinto que pode ser melhorado, só não sei como e por isso peço a ajuda de vocês.

Valeu!

13 Respostas

renzonuccitelli

Esse é um dos problemas do padrão Observer. Abordei justamente esses problemas no meu TCC. Como estudo de caso e proposta de solução, utilizei o SAX como parâmetro, produzindo um parser de xml baseado em metadados(Anntoation)+Reflection, o JColtrane. Se vc conhece o SAX vai dar pra entender.

renzonuccitelli

To anexando os slides que montei para a apresentacao do trabalho, acho que vai ficar mais fácil.

Reilander

Cara, não entendi muito bem sua solução não. Pode até ser boa, mas parece que é usar metralhadora pra matar barata. Queria uma alternativa mais simples.

Valeu!

B

O problema me lembra o Command e o Chain of Responsability patterns.

Eu faria um switch pro getSource, onde cada um dele mandaria pra um método diferente, depois dentro deles um switch p/ tratar os keyCodes.

Reilander

Mas aí deu no mesmo, apenas quebrei os vários if/elses que cobria source e keyCode em dois métodos, um pra cada um. E switch e if/else também é a mesma coisa. Eu quero removê-los.

Eu estou desenvolvendo uma forma aqui e daqui a pouco posto o código pra galera avaliar.

Valeu!

B

Não, realmente a solução que mudei não foi pra implementar algo especial a mais, e sim para limpar o código e torná-lo mais fácil de ser desenvolvido.

Ataxexe

Para remover os ifs dos alvos você pode usar Listeners específicos para cada alvo. Quanto ao keypressed você pode criar um Listener para o seu projeto, exemplo:

public interface MeuListener extends KeyListener { void enterPressed(KeyEvent event); void escapePressed(KeyEvent event); }

renzonuccitelli
Reilander:
Cara, não entendi muito bem sua solução não. Pode até ser boa, mas parece que é usar metralhadora pra matar barata. Queria uma alternativa mais simples.

Valeu!

Vc chegou a olhar a apresentação? Nela eu apresento um exemplo de MouseListener que poderia ser feito. E sim, a solução que mandei não é trivial, mas depois de implementada, fica muito fácil de usar. Olhá a comparação dos códigos abaixo:

Parse de xml com SAX usando o padrão Observer tradicional:

public class WorkflowXMLHandler extends DefaultHandler {
	
	private static String[] actionListTypes = {"onEnter","onLeave","onError","trigger"};
	
	private WorkflowDefinition workflow;
	private State currentState;
	
	private List actionList;
	private String listType;
	private String triggerName;

	public void endElement(String uri, String localName, String qName) throws SAXException {
		if(localName.equals("state")){
			workflow.addState(currentState);
			currentState = null;
		}
		if(isActionList(localName)){
			if(listType.equals("onEnter")){
				currentState.setOnEnter(actionList);
			} else if(listType.equals("onLeave")){
				currentState.setOnLeave(actionList);
			} else if(listType.equals("onError")){
				currentState.setOnError(actionList);
			} else if(listType.equals("trigger")){
				currentState.addTrigger(triggerName, actionList);
			} 
		}
	}

	public void startDocument() throws SAXException {
		workflow = new WorkflowDefinition();
	}

	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if(localName.equals("workflow")){
			setWorkflowProperties(attributes);
		}
		if(localName.equals("state")){
			setStateAttributes(attributes);
		}
		if(isActionList(localName)){
			actionList = new ArrayList();
			listType = localName;
			if(listType.equals("trigger"))
				triggerName = attributes.getValue("name");
		}
		if(localName.equals("action")){
			createAction(attributes);
		}
		if(localName.equals("nextState")){
			createNextState(attributes);
		}
	}

	private void createNextState(Attributes attributes) {
		NextState nextState = new NextState();
		nextState.setStateName(attributes.getValue("name"));
		nextState.setToCompareValue(attributes.getValue("toCompareValue"));
		nextState.setEnterAndLeave("true".equalsIgnoreCase(attributes.getValue("enterAndLeave")));
		nextState.setToCompareProperty(attributes.getValue("toCompareProperty"));
		nextState.setProperty(attributes.getValue("property"));
		String evaluationStrategy = attributes.getValue("evaluationStrategy");
		if("GREATTER".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.GREATTER);
		else if("LESSER".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.LESSER);
		else if("LESSER_EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.LESSER_EQUALS);
		else if("GREATTER_EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.GREATTER_EQUALS);
		else if("EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.EQUALS);
		currentState.addNextState(nextState);
	}

	private void createAction(Attributes attributes) {
		GenericAction action = ActionFactory.createAction(attributes.getValue("name"));
		for(int i=0;i<attributes.getLength();i++){
			if(!attributes.getLocalName(i).equals("name")){
				BeanUtils.setProperty(action, attributes.getLocalName(i), attributes.getValue(i));
			}
		}
		actionList.add(action);
	}

	private boolean isActionList(String localName) {
		return Arrays.binarySearch(actionListTypes, localName) >= 0;
	}

	private void setStateAttributes(Attributes attributes) {
		currentState = new State();
		currentState.setName(attributes.getValue("name"));
		if(attributes.getValue("isInitialState") != null){
			currentState.setInitialState(attributes.getValue("isInitialState").equalsIgnoreCase("true"));
		}
		if(attributes.getValue("isFinalState") != null){
			currentState.setFinalState(attributes.getValue("isFinalState").equalsIgnoreCase("true"));
		}
	}

	private void setWorkflowProperties(Attributes attributes) {
		try {
			workflow.setWorkflowClass(Class.forName(attributes.getValue("class")));
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("The workflow class "+attributes.getValue("class")+" not found",e);
		}
		workflow.setIdAttribute(attributes.getValue("idProperty"));
		workflow.setWorkflowId(attributes.getValue("id"));
		workflow.setDefaultPersistStrategy("true".equalsIgnoreCase(attributes.getValue("persistDefault")));
	}
	
	public WorkflowDefinition getWorkflow(){
		return workflow;
	}
}

Mesmo parsing, utilizando o padrão MetaObservador:

public class WorkflowJColtraneXMLHandler {
	private WorkflowDefinition workflow;
	private State currentState;
	private List actionList;
	
	@StartDocument
	private void startDocument() {
		workflow = new WorkflowDefinition();
	}
	
	@EndElement(localName="state")
	private void addStateToWorkFlow(){
		workflow.addState(currentState);
		currentState = null;
	}
	
	@EndElement(localName="onEnter")
	private void addStateOnEnterActionsToState(){
		currentState.setOnEnter(actionList);
	}
	
	@EndElement(localName="onLeave")
	private void addStateOnLeaveActionsToState(){
		currentState.setOnLeave(actionList);
	}
	
	@EndElement(localName="onError")
	private void addStateOnErrorActionsToState(){
		currentState.setOnLeave(actionList);
	}
	
	@EndElement(localName="trigger")
	private void addStateTriggerActionsToState(@Attribute("name") String triggerName){
		currentState.addTrigger(triggerName, actionList);
	}
	
	@BeforeElement(localName="onEnter||onLeave||onError||trigger", elementDeep = 1)
	@StartElement(localName="action")
	private void buildOnEnterActions(@AttributeMap Map<String,String> attributes){
		createAction(attributes);
	}
	
	private void createAction(Map<String,String> attributes) {
		GenericAction action = ActionFactory.createAction(attributes.get("name"));
		for(String attName:attributes.keySet()){
			if(!attName.equals("name")){
				BeanUtils.setProperty(action, attName, attributes.get(attName));
			}
		}
		actionList.add(action);
	}
	
	@StartElement(localName="onEnter||onLeave||onError||trigger")
	private void initializeActionList(){
		actionList = new ArrayList();
	}
	
	@StartElement(localName="nextState")
	private void createNextState(@AttributeMap Map<String,String> attributes) {
		NextState nextState = new NextState();
		nextState.setStateName(attributes.get("name"));
		nextState.setToCompareValue(attributes.get("toCompareValue"));
		nextState.setEnterAndLeave("true".equalsIgnoreCase(attributes.get("enterAndLeave")));
		nextState.setToCompareProperty(attributes.get("toCompareProperty"));
		nextState.setProperty(attributes.get("property"));
		String evaluationStrategy = attributes.get("evaluationStrategy");
		if("GREATTER".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.GREATTER);
		else if("LESSER".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.LESSER);
		else if("LESSER_EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.LESSER_EQUALS);
		else if("GREATTER_EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.GREATTER_EQUALS);
		else if("EQUALS".equals(evaluationStrategy))
			nextState.setConditionType(ConditionType.EQUALS);
		currentState.addNextState(nextState);
	}
	
	@StartElement(localName="state",priority=1)
	private void setState(@Attribute("name") String stateName) {
		currentState = new State();
		currentState.setName(stateName);
	}
	
	@StartElement(localName="state",attributes={@ContainAttribute(name="isInitialState")})
	private void setInitialStateAttributes(@Attribute("isInitialState") boolean isInitialState) {
		currentState.setInitialState(isInitialState);
	}
	
	@StartElement(localName="state",	attributes={@ContainAttribute(name="isFinalState")})
	private void setFinalStateAttributes(@Attribute("isFinalState") boolean isFinalState) {
		currentState.setFinalState(isFinalState);
	}
	
	@StartElement(localName="workflow")
	private void setWorkflowProperties(@Attribute("class") String clazz,@Attribute("idProperty") String idProperty,
			@Attribute("id") String id,@Attribute("persistDefault") boolean persistDefault) {
		try {
			workflow.setWorkflowClass(Class.forName(clazz));
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("The workflow class "+clazz+" not found",e);
		}
		workflow.setIdAttribute(idProperty);
		workflow.setWorkflowId(id);
		workflow.setDefaultPersistStrategy(persistDefault);
	}
	
	public WorkflowDefinition getWorkflow(){
		return workflow;
	}
}

Dê uma olhada como o segundo código fica mais legível, sem tantos blocos if/else aninhados...

Lavieri

vc poderia usar um objeto enum (Enumeration) como solução, para remoção destes ifs todos …

eu acho uma solução bem elegante

renzonuccitelli

Lavieri:
vc poderia usar um objeto enum (Enumeration) como solução, para remoção destes ifs todos …

eu acho uma solução bem elegante

Fiquei curioso, não consegui vislumbrar como usar Enumeration sem blocos if else. Só o que consegui imaginar foi implementar um Listener que setasse um Enum pra vc poder usar switch depois. Mas mesmo assim, na hora de setar o Enum, vc teria que usar os blocos else if, e depois, usando blocos switch, acho que a granularidade ainda ficaria comprometida. Foi isso que vc tinha em mente, Lavieri, ou outra idéia?

T

Reilander:
Sempre vejo (e faço) códigos de tratamentos de evento do tipo:

public void keyPressed(KeyEvent e) {
   if (e.getSource() == <componente-da-view>) {
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
         // processa evento <ENTER> para esse componente
      }
      else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
         // processa evento <ESCAPE> para esse componente
      }
   }
   else if (e.getSource() == <outro-componente-da-view>) {
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
         // processa evento <ENTER> para esse outro componente
      }
      else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
         // processa evento <ESCAPE> para esse outro componente
      }
   }
   ...
}

No caso específico de ENTER (para ir para o próximo campo, como se fosse um TAB) e ESC (para fechar o form), o correto é fazer como descrevo neste post. Assim não é necessário ter um keyPressed gigantesco, que é dependente de TODOS os componentes do form, e que deve ser alterado CADA VEZ que você acrescenta ou tira um componente do form.

EDIT - Aham, falha nossa - o post é:
http://www.guj.com.br/posts/list/92862.java#497162
e
http://www.guj.com.br/posts/list/92862.java#497384

renzonuccitelli

Thingol, vc esqueceu de colocar o link…

T

http://www.guj.com.br/posts/list/92862.java#497162
http://www.guj.com.br/posts/list/92862.java#497384

Criado 18 de janeiro de 2009
Ultima resposta 19 de jan. de 2009
Respostas 13
Participantes 6