18 ago 2011

GWT Command para ejecución secuencial

Con GWT hay que estar atentos al hecho de que la programación esta dirigida por eventos y de manera totalmente asíncrona, por lo que no solo debemos basarnos en ellos para hacer fluir la aplicación sino que tampoco podemos fiarnos de que la lógica que necesitemos haya sido correctamente implementada por las sentencias anteriores.

Precisamente esto mismo me paso al realizar una serie de métodos secuenciales: la lógica necesaria para pasar al siguiente método aún no estaba disponible, por lo que al avanzar la cadena de ejecución fallaba miserablemente ya que no verificaba que un paso estuviera totalmente finalizado para avanzar al siguiente.
Una posible solución es que al ejecutar un método si todo esta correcto invoquemos el próximo, pero no queda muy prolijo que digamos, fácilmente podemos tener una ensalada de llamadas por doquier, ni hablar si queremos cambiar el flujo de la aplicación, suerte en pila.

Una solución que da buenos resultados es utilizar los Comandos, con ellos es posible encapsular determinada lógica y luego ejecutarla a demanda, si a esto lo aderezamos con iteradores y ArrayLists, la cosa empieza a tomar otro color, veamos un ejemplo completo y luego lo diseccionamos:

package com.myPackage.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;

public class DemoComandos {

	private List<Command> commandsStack = new ArrayList<Command>();
	private Command command_01, command_02;
	private Iterator<Command> cmdIterator;


	public Comandos(){
		loadCommandsStack();
		run();
	}
	

	private void loadCommandsStack(){

		command_01 = new Command() {
			@Override
			public void execute() {
				
				boolean ok_01 = true;
				if(ok_01){
					run();
				}
				else{
					Window.alert("error comando 1");
				}
				
			}
		};

		
		command_02 = new Command() {
			@Override
			public void execute() {

				boolean ok_02 = false;

				if(ok_02){
					run();
				}
				else{
					Window.alert("error comando 2");
				}
				
			}
		};
		
		
		commandsStack.add(command_01);
		commandsStack.add(command_02);
		cmdIterator = commandsStack.iterator();
	}
	
	
	private void run(){
		
		if(!cmdIterator.hasNext()){
			Window.alert("no hay mas comandos");
		}
		else{
			cmdIterator.next().execute();
		}
	}
	
}


Veamos por partes, la idea es encapsular nuestra lógica en comandos y agrupar estos en una lista, para mantenerlo simple dentro de la ejecución de estos no llamaremos métodos externos pero es lo más recomendable para separar la lógica de las sentencias y tenerlas definidas aparte.
Al crear una lista de comandos no solo unificamos ya que ahora todos son del mismo tipo, sino que podemos verificar que un paso esta completo antes de llamar al siguiente, pera hacerlo dinámicamente creamos un iterador que sea invocado por los propios métodos si estos finalizan correctamente, en caso contrario la aplicación se detiene ya que cortamos la cadena de llamadas. Ahora en detalle:

	private List<Command> commandsStack = new ArrayList<Command>();
	private Command command_01, command_02;
	private Iterator<Command> cmdIterator;

aquí creamos la lista que contendrá nuestros comandos, la definimos del tipo ArrayList para manejarlos mejor, luego creamos los comandos y por último definimos un iterador, no lo inicializaremos hasta tener cargados los comandos en la lista.

public Comandos(){
        loadCommandsStack();
        run();
    }


Por sencillez ejecuto en el constructor el método para la carga de los comandos, solo agrupa esta operación para mantenerlo conciso pero podemos colocarlo en cualquier lado, lo hacemos así:


command_01 = new Command() {

	@Override
	public void execute() {
		// aquí ejecutamos la rutina o llamamos a un método aparte
		// si todo es correcto invocamos el método run() y continuamos

			boolean ok_01 = true; // simulamos que todo fué Ok
			if(ok_01){
			    run();
			}
			else{
			    // en caso contrario la aplicación se detiene
			    Window.alert("error comando 1");
			}
				
		}
	};


Como mencionaba para agregarle flexibilidad a la ejecución de la pila de llamadas agregamos los comandos a un ArrayList e inicializamos el iterador:

commandsStack.add(command_01);
commandsStack.add(command_02);
cmdIterator = commandsStack.iterator();


Ahora lo interesante, el iterador posee un método hasNext() que nos indica si hay un comando próximo cargado a ejecutar, si no es el caso quiere decir que completamos la pila de llamadas, este lo encapsulamos en un método run(), al llamarlo por primera vez dispara la secuencia de ejecución, luego esta es mantenida por los resultados de los siguientes métodos, es decir, si estos se efectuan correctamente invocan sucesivamente run() continuando la secuencia, si ocurre un error la cedena de llamadas se detiene y si no todo concluye correctamente.


private void run(){
	
    if(!cmdIterator.hasNext()){
        Window.alert("no hay mas comandos");
    }
    else{
        cmdIterator.next().execute();
    }
}

Inclusive podemos tener una clase maestra que contenga un metodo run()  general y clases inferiores más específicas con la misma estructura, éstas al concluir sus comandos locales si tienen una referencia a la clase maestra pueden invocar al run() general en vez del local y continuar la pila de llamadas a otras clases inferiores.

En mi caso utilizé este planteo con una clase maestra llamada Init que invoca varias clases menores que realizan tareas específicas como chequear la conexión, crear la GUI, obtener JavaScript onDemand u otros, cada una tiene una referencia a Init que luego de realizar todos sus comandos ejecutan Init.run() y pasan la posta a la pila de llamadas contenidas en otra clase.Otra ventaja que vuelvo a mencionar es la facilidad para cambiar el flujo de la aplicación, basta con comentar el commandsStack.add(myCommand) que queramos y todo seguirá fluyendo normalmente salteándose ese paso, creo que solo aporta ventajas así que vale la pena probarlo.

No hay comentarios: