Estructura del programa
Esta es la forma en que lo hice. El Eventbus
permite a los presentadores (que extienden la clase abstracta Subscriber
) suscribirse a eventos pertenecientes a los diferentes módulos en mi aplicación. Cada módulo corresponde a un componente en mi sistema, y cada módulo tiene un tipo de evento, un presentador, un controlador, una vista y un modelo.
Un presentador que se suscriba a todos los eventos del tipo CONSOLE
recibirá todos los eventos activados desde ese módulo. Para un enfoque más refinado, siempre puedes permitir que los presentadores se suscriban a eventos específicos, como NewLineAddedEvent
o algo así, pero a mí me pareció que lidiar con eso en un nivel de módulo era lo suficientemente bueno.
Si lo desea, puede hacer que la llamada a los métodos de rescate del presentador sea asíncrona, pero hasta ahora no he tenido necesidad de hacerlo. Supongo que depende de cuáles sean tus necesidades exactas. Este es mi EventBus
:
public class EventBus implements EventHandler
{
private final static EventBus INSTANCE = new EventBus();
private HashMap<Module, ArrayList<Subscriber>> subscribers;
private EventBus()
{
subscribers = new HashMap<Module, ArrayList<Subscriber>>();
}
public static EventBus get() { return INSTANCE; }
public void fire(ScEvent event)
{
if (subscribers.containsKey(event.getKey()))
for (Subscriber s : subscribers.get(event.getKey()))
s.rescue(event);
}
public void subscribe(Subscriber subscriber, Module[] keys)
{
for (Module m : keys)
subscribe(subscriber, m);
}
public void subscribe(Subscriber subscriber, Module key)
{
if (subscribers.containsKey(key))
subscribers.get(key).add(subscriber);
else
{
ArrayList<Subscriber> subs = new ArrayList<Subscriber>();
subs.add(subscriber);
subscribers.put(key, subs);
}
}
public void unsubscribe(Subscriber subscriber, Module key)
{
if (subscribers.containsKey(key))
subscribers.get(key).remove(subscriber);
}
}
Los manipuladores están unidos a los componentes, y son responsables de la transformación de eventos nativos GWT en eventos especializados para mi sistema. El controlador a continuación trata de ClickEvents
simplemente envolviéndolos en un evento personalizado y activándolos en el EventBus
para que los suscriptores los traten. En algunos casos, tiene sentido que los manejadores realicen comprobaciones adicionales antes de disparar el evento, o incluso antes de decidir el clima o no enviar el evento. La acción en el controlador se da cuando el controlador se agrega al componente gráfico.
public class AppHandler extends ScHandler
{
public AppHandler(Action action) { super(action); }
@Override
public void onClick(ClickEvent event)
{
EventBus.get().fire(new AppEvent(action));
}
Action
es una enumeración expresa posibles formas de manipulación de datos en mi sistema. Cada evento se inicializa con un Action
. La acción es utilizada por los presentadores para determinar cómo actualizar su vista. Un evento con la acción ADD
podría hacer que un presentador agregue un nuevo botón a un menú, o una nueva fila a una grilla.
public enum Action
{
ADD,
REMOVE,
OPEN,
CLOSE,
SAVE,
DISPLAY,
UPDATE
}
El evento que es despedido por el controlador se ve así. Observe cómo el evento define una interfaz para sus consumidores, lo que le asegurará que no se olvide de implementar los métodos de rescate correctos.
public class AppEvent extends ScEvent {
public interface AppEventConsumer
{
void rescue(AppEvent e);
}
private static final Module KEY = Module.APP;
private Action action;
public AppEvent(Action action) { this.action = action; }
El presentador se suscribe a eventos que pertenecen a la diversa módulos, y luego los rescata cuando son despedidos. También dejo que cada presentador defina una interfaz para su vista, lo que significa que el presentador nunca tendrá que saber nada sobre los componentes gráficos reales.
public class AppPresenter extends Subscriber implements AppEventConsumer,
ConsoleEventConsumer
{
public interface Display
{
public void openDrawer(String text);
public void closeDrawer();
}
private Display display;
public AppPresenter(Display display)
{
this.display = display;
EventBus.get().subscribe(this, new Module[]{Module.APP, Module.CONSOLE});
}
@Override
public void rescue(ScEvent e)
{
if (e instanceof AppEvent)
rescue((AppEvent) e);
else if (e instanceof ConsoleEvent)
rescue((ConsoleEvent) e);
}
}
Cada vista se da una instancia de un HandlerFactory
que es responsable de crear el tipo correcto de controlador para cada vista. Cada fábrica está instanciada con un Module
, que se utiliza para crear controladores del tipo correcto.
public ScHandler create(Action action)
{
switch (module)
{
case CONSOLE :
return new ConsoleHandler(action);
La vista es ahora libre para agregar controladores de tipo diferente a sus componentes sin tener que saber sobre los detalles exactos de aplicación.En este ejemplo, toda la vista necesita saber que el botón addButton
debe estar vinculado a algún comportamiento correspondiente a la acción ADD
. Lo que este comportamiento es será decidido por los presentadores que atrapen el evento.
public class AppView implements Display
public AppView(HandlerFactory factory)
{
ToolStripButton addButton = new ToolStripButton();
addButton.addClickHandler(factory.create(Action.ADD));
/* More interfacy stuff */
}
public void openDrawer(String text) { /*Some implementation*/ }
public void closeDrawer() { /*Some implementation*/ }
Ejemplo
Considere un eclipse simplificado donde se tiene una jerarquía de clases a la izquierda, un área de texto de código de la derecha, y una barra de menú en la parte superior. Estos tres serían tres vistas diferentes con tres presentadores diferentes y, por lo tanto, conformarían tres módulos diferentes. Ahora, es completamente posible que el área de texto tenga que cambiar de acuerdo con los cambios en la jerarquía de clases, y por lo tanto tiene sentido que el presentador del área de texto se suscriba no solo a los eventos que se dispararon desde el área de texto, sino también a los eventos ser despedido de la jerarquía de la clase. Me puedo imaginar algo como esto (por cada módulo habrá un conjunto de clases - un manipulador, un tipo de evento, un presentador, un modelo y un punto de vista):
public enum Module
{
MENU,
TEXT_AREA,
CLASS_HIERARCHY
}
Consideremos ahora queremos que nuestros puntos de vista para actualizar correctamente al eliminar un archivo de clase de la vista de jerarquía. Esto debería dar lugar a los siguientes cambios en la interfaz gráfica de usuario:
- El archivo de clase debe ser eliminado de la jerarquía de clases
- Si se abre el archivo de clase, y por lo tanto visible en el área de texto, se debe cerrar.
Dos presentadores, el que controla la vista en árbol y el que controla la vista de texto, se suscribirían a los eventos disparados desde el módulo CLASS_HIERARCHY
. Si la acción del evento es REMOVE
, ambos preseneters podrían tomar la acción apropiada, como se describió anteriormente. El presentador que controla la jerarquía presumiblemente también enviará un mensaje al servidor, asegurándose de que el archivo eliminado realmente se eliminó. Esta configuración permite que los módulos reaccionen a eventos en otros módulos simplemente escuchando eventos disparados desde el bus de eventos. Hay muy poco acoplamiento, y el intercambio de puntos de vista, presentadores o controladores es completamente indoloro.
El equipo de GWT publicó una aplicación de ejemplo de la charla sobre mejores prácticas de GWT en Google IO 2009 que usa MVP, y EventBus - http://code.google.com/webtoolkit/articles/mvp-architecture.html – Anurag