是否有推荐的方法可以在 MVP 中使用 GWT 的观察者模式?程序结构例

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

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

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

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

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


答案 1

程序结构

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

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

如果你愿意,你可以把对演示者的救援方法的调用异步化,但到目前为止,我发现自己几乎不需要这样做。我想这取决于你的确切需求是什么。这是我的: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);
    }

}

处理程序附加到组件,并负责将本机 GWT 事件转换为专用于我的系统的事件。下面的处理程序只需将它们包装在自定义事件中并在 上触发它们以供订阅者处理即可处理。在某些情况下,处理程序在触发事件之前执行额外的检查是有意义的,有时甚至在决定天气或不发送事件之前。处理程序中的操作是在将处理程序添加到图形组件时给出的。ClickEventsEventBus

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

    @Override
    public void onClick(ClickEvent event) 
    { 
         EventBus.get().fire(new AppEvent(action)); 
    }

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

public enum Action 
{
    ADD,
    REMOVE,
    OPEN,
    CLOSE,
    SAVE,
    DISPLAY,
    UPDATE
}

由处理程序触发的事件看起来有点像这样。请注意事件如何为其使用者定义接口,这将确保您不要忘记实现正确的救援方法。

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; }

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

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

每个视图都有一个实例,负责为每个视图创建正确类型的处理程序。每个工厂都用 一个 实例化,它用它来创建正确类型的处理程序。HandlerFactoryModule

public ScHandler create(Action action)
{
  switch (module)
  {
    case CONSOLE :
      return new ConsoleHandler(action);

视图现在可以自由地向其组件添加不同类型的处理程序,而无需了解确切的实现细节。在此示例中,视图需要知道的是,按钮应链接到与操作相对应的某个行为。此行为是什么将由捕获事件的演示者决定。addButtonADD

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,其中左侧有一个类层次结构,右侧有一个代码文本区域,顶部有一个菜单栏。这三个将是三个不同的视图,有三个不同的演示者,因此它们将组成三个不同的模块。现在,文本区域完全有可能需要根据类层次结构中的更改进行更改,因此文本区域表示器不仅要订阅从文本区域内触发的事件,还要订阅从类层次结构触发的事件是有意义的。我可以想象这样的事情(对于每个模块,将有一组类 - 一个处理程序,一个事件类型,一个演示器,一个模型和一个视图):

public enum Module 
{
   MENU,
   TEXT_AREA,
   CLASS_HIERARCHY
}

现在考虑一下,我们希望在从层次结构视图中删除类文件时,我们的视图能够正确更新。这将导致对 gui 进行以下更改:

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

两个表示器(一个控制树视图,另一个控制文本视图)都将订阅从模块触发的事件。如果事件的动作是 ,则两个先验者都可以采取适当的动作,如上所述。控制层次结构的演示者可能还会向服务器发送一条消息,确保已删除的文件实际上已被删除。此设置允许模块通过侦听从事件总线触发的事件来对其他模块中的事件做出反应。几乎没有耦合,交换视图,演示者或处理程序是完全无痛的。CLASS_HIERARCHYREMOVE


答案 2

我为我们的项目在这些方面取得了一些成就。我想要一个事件驱动的机制(想想标准jdk lib的PropertyChangeSupport和PropertyChangeListener),它们丢失了。我相信有一个扩展模块,并决定继续我自己的模块。你可以谷歌它的属性变化支持gwt,并使用它或采用我的方法。

我的方法涉及以MessageHandler和GWTEvent为中心的逻辑。它们分别用于与 PropertyChangeListener 和 PropertyChangeEvent 相同的目的。出于后面解释的原因,我不得不自定义它们。我的设计涉及MessageExchange,MessageSender和MessageListener。交换充当广播服务,将所有事件分派给所有侦听器。每个发送方触发 Exchange 侦听的事件,交换方再次触发这些事件。每个侦听器侦听交换,并可以根据事件自行决定(处理或不处理)。

不幸的是,GWT中的MessageHandlers遇到了一个问题:“当一个事件被消耗时,没有新的处理程序可以被挂接”。GWT 表单中给出的原因:保存处理程序的支持迭代器不能由另一个线程同时修改。我不得不重写GWT类的自定义实现。这是基本思想。

我本来会发布代码,但我现在正在去机场的路上,我会尽快发布代码,只要我能腾出时间。

编辑1:

由于还无法获得实际的代码,拿到了一些我正在为设计文档制作的Power-Point幻灯片,并创建了一个博客条目。

发布指向我的博客文章的链接:GXT-GWT应用程序

编辑2:

最后是一些代码汤。发布 1 发布 2 发布 3


推荐