java - 是否有使用 GWT 在 MVP 中使用观察者模式的推荐方法?

标签 java design-patterns gwt observer-pattern

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

这些是(部分)我的目标:

  • 演示者对 UI 技术一无所知(即不使用 com.google.* 中的任何内容)
  • 该 View 对演示者一无所知(尚不确定我是否希望它与模型无关)
  • 模型对 View 或演示者一无所知(...显然)

  • 我会在 View 和演示者之间放置一个接口(interface),并使用观察者模式将两者解耦: View 生成事件并通知演示者。

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

    最佳答案

    程序结构

    我就是这样做的。 Eventbus让演示者(扩展抽象类 Subscriber )订阅属于我的应用程序中不同模块的事件。每个模块对应我系统中的一个组件,每个模块都有一个事件类型、一个展示器、一个处理程序、一个 View 和一个模型。

    订阅 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);
        }
    
    }
    

    处理程序附加到组件,负责将 native 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)); 
        }
    
    Action是一个枚举,表示在我的系统中可能的数据操作方式。每个事件都用 Action 初始化.演示者使用该操作来确定如何更新他们的 View 。带有 Action 的事件 ADD可能会让演示者向菜单添加一个新按钮,或向网格添加新行。
    public enum Action 
    {
        ADD,
        REMOVE,
        OPEN,
        CLOSE,
        SAVE,
        DISPLAY,
        UPDATE
    }
    

    处理程序触发的事件看起来有点像这样。请注意事件如何为其使用者定义接口(interface),这将确保您不会忘记实现正确的救援方法。
    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; }
    

    演示者订阅属于不同模块的事件,然后在它们被触发时拯救它们。我还让每个演示者为其 View 定义一个接口(interface),这意味着演示者永远不必了解有关实际图形组件的任何信息。
    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);
        }
    }
    

    每个 View 都有一个 HandlerFactory 的实例负责为每个 View 创建正确类型的处理程序。每个工厂都使用 Module 实例化,它用于创建正确类型的处理程序。
    public ScHandler create(Action action)
    {
      switch (module)
      {
        case CONSOLE :
          return new ConsoleHandler(action);
    

    View 现在可以自由地向其组件添加不同类型的处理程序,而无需了解确切的实现细节。在这个例子中,所有 View 需要知道的是 addButton按钮应该链接到与 Action 对应的某些行为 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*/ }
    

    例子

    考虑一个简化的 Eclipse,其中左侧有一个类层次结构,右侧有一个代码文本区域,顶部有一个菜单栏。这三个将是具有三个不同演示者的三个不同 View ,因此它们将组成三个不同的模块。现在,文本区域完全有可能需要根据类层次结构的变化进行更改,因此文本区域演示者不仅订阅从文本区域内触发的事件,而且订阅事件是有意义的从类层次结构中被解雇。我可以想象这样的事情(对于每个模块都会有一组类——一个处理程序、一个事件类型、一个演示者、一个模型和一个 View ):
    public enum Module 
    {
       MENU,
       TEXT_AREA,
       CLASS_HIERARCHY
    }
    

    现在考虑我们希望我们的 View 在从层次结构 View 中删除类文件时正确更新。这应该会导致对 gui 的以下更改:
  • 应该从类层次结构中删除类文件
  • 如果类文件已打开,因此在文本区域中可见,则应将其关闭。

  • 两个演示者,一个控制树 View ,另一个控制 TextView ,都将订阅从 CLASS_HIERARCHY 触发的事件。模块。如果事件的 Action 是REMOVE ,两个演示者都可以采取适当的操作,如上所述。控制层次结构的演示者可能还会向服务器发送消息,以确保已删除的文件实际上已被删除。这种设置允许模块简单地通过监听从事件总线触发的事件来对其他模块中的事件使用react。几乎没有耦合,交换 View 、演示者或处理程序是完全无痛的。

    关于java - 是否有使用 GWT 在 MVP 中使用观察者模式的推荐方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2832779/

    相关文章:

    Java 编译错误 : switch on enum

    php - 为什么类名不回退到命名空间中的全局类名?

    css - 使用 TabLayoutPanel 和 GWT 的时髦布局问题?

    javascript - gwt编译器找不到入口点类

    java - 在 spring mvc 中创建自定义注释并获取 httpservletrequest 对象

    java - Hibernate 无法提取结果集异常 - 多对多关系

    ios - 将可变类更改为不可变类

    java - 如何在没有模型类的情况下从对象在 GXT 中创建网格?

    java - 使用 Apache Sqoop 从 MySQL 导入数据 - 错误 : No manager for connect string

    ruby - 如何在 Ruby 中实现查找类?