2010-05-14 17 views
16

Estoy pensando en implementar una interfaz de usuario según el patrón MVP usando GWT, pero tengo dudas sobre cómo proceder.¿Existe alguna forma recomendada de utilizar el patrón Observer en MVP usando GWT?

Estos son (algunos de) mis metas: (. Es decir, utiliza nada de com.google *)

  • el presentador no sabe nada acerca de la tecnología de interfaz de usuario
  • la vista no sabe nada sobre el presentador (no seguro todavía si me gustaría que fuera modelo agnóstico, aún)
  • el modelo no sabe nada de la vista o el presentador (... obviamente)

me pondría una interfaz entre la vista y el presentador y use 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?

+3

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

Respuesta

31

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:

  1. El archivo de clase debe ser eliminado de la jerarquía de clases
  2. 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.

+0

Este es un enfoque bastante elaborado y una respuesta extensa, gracias. ¿Podría ampliarlo un poco más explicando cuál es el rol del Suscriptor? Además, ¿cuál es la idea detrás de suscribir un presentador a los módulos? En la forma en que estoy pensando, un presentador está fuertemente relacionado y localizado con un par de modelo de vista específico. Además, utiliza una sola clase de Acción para definir todas las acciones que se pueden desencadenar desde cualquier parte de la IU: ¿se vuelve complicado cuando suficientes clases agregan sus acciones? Además, eso significa que los cambios en cualquier módulo afectarán a Acción, lo que suena un poco preocupante ... –

+0

He editado mi respuesta para responder a sus preguntas de seguimiento. –

+0

Banang, gracias por el esfuerzo y la comprensión. Lo intentaré y veré cómo va ... –

2

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, los MessageHandlers en GWT tienen 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 estoy en camino al aeropuerto en este momento, intentaré publicar el código tan pronto como pueda.

Edit1:

Todavía no poder obtener el código real, apoderado de algunas diapositivas de Power Point que estaba trabajando para la documentación de proyectos y creado una entrada en el blog.

Añadir un enlace a mi artículo de blog: GXT-GWT App

Edit2:

Finalmente algo de código sopa. Posting 1 Posting 2 Posting 3

+0

Gracias por su responder. ¿Podría comparar su MessageExchange con el EventBus de Banang? Por el momento no estoy seguro de qué es lo que obtienes al hacer que todos los oyentes evalúen cada evento de la aplicación para ver si es algo que deben manejar, que es así como entendí el enfoque de MessageExchange para funcionar ... –

+0

Respuesta rápida; algunos lo llaman potaatoe, algunos lo llaman potaytoe! Realmente no traté de lograr MVP en primer lugar. Estaba tratando de lograr una solución impulsada por eventos. Mi miembro del equipo me dirigió luego hacia el artículo de Martin Fowler sobre MVP. Me alegré de que el diseño fuera sensato y que realmente encajara en un paradigma. La diferencia clave de MVP: En la vista de MVP, se escucha a los presentadores. En mi caso, cualquier entidad puede escuchar cualquiera (encadenamiento de oyentes/modelos).La comunicación es a través de eventos Excepción: Comunicación del lado del servidor, escribí una capa de controlador de despacho (por unidad de prueba) cont.: – questzen

+0

Rational: Visualicé mi diseño como una central telefónica. En el mundo real, no podemos tener una línea telefónica entre todos los teléfonos: líneas NC2 o N (N-1)/2. Nos conectamos a un intercambio por lo que solo se necesitan N líneas. Piense en cada línea como una referencia al oyente, la propagación de la referencia del objeto se reduciría drásticamente. IMO, MVP en realidad no solicita un intercambio/autobús centralizado. Uno puede ver fácilmente el beneficio del bus de eventos (mientras codifica) y usarlo. La solución de Banang es buena. Mi solución se basó en la composición donde ella utilizó la herencia. Una diferencia de opinión, supongo. – questzen

Cuestiones relacionadas