strategy pattern patrones patron observer implementacion funciona ejemplos ejemplo diseño diagrama como java design-patterns gwt observer-pattern

java - pattern - ¿Hay alguna forma recomendada de usar el patrón Observer en MVP usando GWT?



patrones de diseño (3)

Estoy pensando en implementar una interfaz de usuario según el patrón de MVP usando GWT, pero tengo dudas sobre cómo proceder.

Estos son (algunos de) mis objetivos:

  • el presentador no sabe nada sobre la tecnología UI (es decir, no usa nada de com.google. *)
  • la vista no sabe nada sobre el presentador (no estoy seguro todavía si me gustaría que sea modelo-agnóstico, aún)
  • el modelo no sabe nada de la vista o del presentador (... obviamente)

Colocaría una interfaz entre la vista y el presentador y usaría el patrón Observer para desacoplar los dos: la vista genera eventos y el presentador recibe una notificación.

Lo que me confunde es que java.util.Observer y java.util.Observable no son compatibles con GWT. Esto sugiere que lo que estoy haciendo no es la manera recomendada de hacerlo, en lo que respecta a GWT, lo que me lleva a formular preguntas: ¿cuál es la forma recomendada de implementar MVP utilizando GWT, específicamente teniendo en cuenta los objetivos anteriores? ¿Como lo harias?


Logré algo en estas líneas para nuestro proyecto. Quería un mecanismo impulsado por eventos (piense en PropertyChangeSupport y PropertyChangeListener de jdk lib estándar) que faltaban. Creo que hay un módulo de extensión y decidí seguir adelante con el mío. Puede googlearlo para propertychangesupport gwt y usarlo o seguir mi enfoque.

Mi enfoque involucró la lógica centrada en MessageHandler y GWTEvent. Estos tienen el mismo propósito que el de PropertyChangeListener y PropertyChangeEvent, respectivamente. Tuve que personalizarlos por las razones explicadas más adelante. Mi diseño involucró un MessageExchange, MessageSender y MessageListener. El intercambio actúa como un servicio de difusión que envía todos los eventos a todos los oyentes. Cada emisor dispara eventos que son escuchados por el Intercambio y el intercambio activa los eventos nuevamente. Cada oyente escucha el intercambio y puede decidir por sí mismo (procesar o no procesar) en función del evento.

Desafortunadamente, MessageHandlers en GWT tiene un problema: "Mientras se consume un evento, no se pueden enganchar nuevos manejadores". Motivo dado en el formulario GWT: El iterador de respaldo que contiene los manejadores no puede ser modificado simultáneamente por otro hilo. Tuve que reescribir la implementación personalizada de las clases de GWT. Esa es la idea básica.

Hubiera publicado el código, pero ahora mismo estoy en camino al aeropuerto e intentaré publicar el código tan pronto como pueda.

Edit1:

Todavía no pude obtener el código real, obtuve algunas diapositivas de power-point en las que estaba trabajando para la documentación de diseño y creé una entrada de blog.

Publicar un enlace al artículo de mi blog: aplicación GXT-GWT

Edit2:

Finalmente algo de sopa de código. Publicación 1 Publicación 2 Publicación 3


Estructura del programa

Así es como lo hice. El Eventbus permite que los presentadores (que extienden el Subscriber clase abstracta) se suscriban a los eventos que pertenecen a 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 manejador, una vista y un modelo.

Un presentador que se suscriba a todos los eventos de 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 asincrónica, 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 transformar los eventos nativos de GWT en eventos especializados para mi sistema. El controlador a continuación trata con ClickEvents simplemente envolviéndolos en un evento personalizado y EventBus en el EventBus para que los suscriban. 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 que expresa posibles formas de manipulación de datos en mi sistema. Cada evento se inicializa con una 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 parece un poco a esto. 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 pertenecientes a diferentes 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 tiene una instancia de HandlerFactory que es responsable de crear el tipo correcto de controlador para cada vista. Cada fábrica se crea una instancia 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 ahora es libre de agregar manejadores de diferente tipo a sus componentes sin tener que conocer los detalles exactos de implementació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 tenga una jerarquía de clases a la izquierda, un área de texto para el código a 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 disparan desde el área de texto, sino también a los eventos ser despedido de la jerarquía de la clase. Puedo imaginar algo así (para cada módulo habrá un conjunto de clases: un manejador, un tipo de evento, un presentador, un modelo y una vista):

public enum Module { MENU, TEXT_AREA, CLASS_HIERARCHY }

Ahora considere que queremos que nuestras vistas se actualicen correctamente al eliminar un archivo de clase de la vista de jerarquía. Esto debería provocar los siguientes cambios en la interfaz gráfica de usuario:

  1. El archivo de clase debe eliminarse de la jerarquía de clases
  2. Si el archivo de clase está abierto y, por lo tanto, visible en el área de texto, debe cerrarse.

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.