是否有推荐的方法在MVP中使用GWT使用Observer模式?

Tom*_*vic 17 java gwt design-patterns observer-pattern

我正在考虑使用GWT根据MVP模式实现用户界面,但对如何继续进行疑问.

这些是(我的一些)目标:

  • 主持人对UI技术一无所知(即不使用com.google.*)
  • 该视图对演示者一无所知(不确定我是否希望它与模型无关)
  • 模型对视图或演示者一无所知(......显然)

我会在视图和演示者之间放置一个接口,并使用Observer模式将两者分离:视图生成事件并通知演示者.

令我困惑的是GWT中不支持java.util.Observer和java.util.Observable.这表明我所做的并不是建议的方式,就GWT而言,这引出了我的问题:使用GWT实施MVP的推荐方法是什么,特别是考虑到上述目标?你会怎么做?

Mia*_*rke 32

计划结构

这就是我做到的.该Eventbus让主持人(扩展抽象类Subscriber)订阅属于不同的事件模块在我的应用程序.每个模块对应于我系统中的一个组件,每个模块都有一个事件类型,一个演示者,一个处理程序,一个视图和一个模型.

订阅所有类型事件的演示者CONSOLE将接收从该模块触发的所有事件.对于更细粒度的方法,您总是可以让演示者订阅特定事件,例如NewLineAddedEvent或类似的东西,但对我来说,我发现在模块级别处理它已经足够了.

如果你想要,你可以异步调用演示者的救援方法,但到目前为止,我发现自己没有必要这样做.我想这取决于你的确切需求.这是我的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);
    }

}
Run Code Online (Sandbox Code Playgroud)

处理程序附加到组件,并负责将本机GWT事件转换为专用于我的系统的事件.下面的处理程序ClickEvents简单地通过将它们包装在一个自定义事件中并将它们发送EventBus给订阅者来处理.在某些情况下,处理程序在触发事件之前执行额外检查是有意义的,有时甚至在决定天气之前还是不发送事件之前.处理程序中的操作是在将处理程序添加到图形组件时给出的.

public class AppHandler extends ScHandler
{
    public AppHandler(Action action) { super(action); }

    @Override
    public void onClick(ClickEvent event) 
    { 
         EventBus.get().fire(new AppEvent(action)); 
    }
Run Code Online (Sandbox Code Playgroud)

Action是一个枚举,表示我的系统中数据操作的可能方式.每个事件都初始化为Action.演示者使用该操作来确定如何更新其视图.具有该操作的事件ADD可能会使演示者向菜单添加新按钮,或向网格添加新行.

public enum Action 
{
    ADD,
    REMOVE,
    OPEN,
    CLOSE,
    SAVE,
    DISPLAY,
    UPDATE
}
Run Code Online (Sandbox Code Playgroud)

被处理程序触发的事件看起来有点像这样.请注意事件如何为其消费者定义接口,这将确保您不会忘记实施正确的救援方法.

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; }
Run Code Online (Sandbox Code Playgroud)

演示者订阅属于不同模块的事件,然后在被触发时拯救它们.我还让每个演示者为它的视图定义一个接口,这意味着演示者不必知道任何有关实际graphcal组件的信息.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

每个视图都有一个实例,HandlerFactory负责为每个视图创建正确类型的处理程序.每个工厂都用a实例化,Module用于创建正确类型的处理程序.

public ScHandler create(Action action)
{
  switch (module)
  {
    case CONSOLE :
      return new ConsoleHandler(action);
Run Code Online (Sandbox Code Playgroud)

该视图现在可以自由地向其组件添加不同类型的处理程序,而无需了解确切的实现细节.在此示例中,需要知道的所有视图都addButton应该将按钮链接到与该操作对应的某些行为ADD.这种行为将由捕获事件的主持人决定.

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*/ }
Run Code Online (Sandbox Code Playgroud)

考虑一个简化的Eclipse,左边是类层次结构,右边是代码的文本区域,顶部是菜单栏.这三个将是三个不同的视图与三个不同的演示者,因此他们组成三个不同的模块.现在,文本区域完全有可能需要根据类层次结构的变化进行更改,因此文本区域演示者不仅可以订阅从文本区域内触发的事件,还可以订阅事件从类层次结构中被触发.我可以想象这样的事情(对于每个模块,将有一组类 - 一个处理程序,一个事件类型,一个演示者,一个模型和一个视图):

public enum Module 
{
   MENU,
   TEXT_AREA,
   CLASS_HIERARCHY
}
Run Code Online (Sandbox Code Playgroud)

现在考虑我们希望在从层次结构视图中删除类文件时正确更新我们的视图.这应该导致gui的以下更改:

  1. 应从类层次结构中删除类文件
  2. 如果类文件已打开,因此在文本区域中可见,则应关闭该文件.

两个演示者,即控制树视图和控制文本视图的演示者,都将订阅从CLASS_HIERARCHY模块触发的事件.如果事件的动作是REMOVE,两个预选者都可以采取适当的行动,如上所述.控制层次结构的演示者可能也会向服务器发送一条消息,确保删除的文件实际上已被删除.此设置允许模块仅通过侦听从事件总线触发的事件来响应其他模块中的事件.几乎没有耦合,交换视图,演示者或处理程序是完全无痛的.