程序结构
我就是这样做的。这Eventbus
让演示者(扩展抽象类Subscriber
) 订阅属于不同的事件modules在我的应用程序中。每个module对应于我系统中的一个组件,每个模块都有一个事件类型、一个呈现器、一个处理程序、一个视图和一个模型。
订阅该类型所有事件的演示者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);
}
}
处理程序附加到组件,并负责将本机 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
。演示者使用该操作来确定如何更新他们的视图。带有动作的事件ADD
可能会让演示者向菜单添加一个新按钮,或者向网格添加一个新行。
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);
}
}
每个视图都有一个实例HandlerFactory
它负责为每个视图创建正确类型的处理程序。每个工厂都实例化了一个Module
,它用于创建正确类型的处理程序。
public ScHandler create(Action action)
{
switch (module)
{
case CONSOLE :
return new ConsoleHandler(action);
该视图现在可以自由地将不同类型的处理程序添加到其组件中,而无需了解确切的实现细节。在此示例中,所有视图需要知道的是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*/ }
Example
考虑一个简化的 Eclipse,其中左侧有一个类层次结构,右侧有一个代码文本区域,顶部有一个菜单栏。这三个将是由三个不同的演示者提供的三个不同的视图,因此它们将组成三个不同的模块。现在,文本区域完全有可能需要根据类层次结构中的更改进行更改,因此文本区域呈现器不仅订阅从文本区域内触发的事件,还订阅事件是有意义的被从类层次结构中解雇。我可以想象这样的事情(对于每个模块,都会有一组类 - 一个处理程序、一个事件类型、一个演示者、一个模型和一个视图):
public enum Module
{
MENU,
TEXT_AREA,
CLASS_HIERARCHY
}
现在考虑一下,我们希望在从层次结构视图中删除类文件时正确更新视图。这应该会导致 GUI 发生以下变化:
- 应该从类层次结构中删除该类文件
- 如果类文件已打开,因此在文本区域中可见,则应将其关闭。
两个演示者(一个控制树视图,一个控制文本视图)都会订阅从CLASS_HIERARCHY
模块。如果事件的动作是REMOVE
,两位演示者都可以采取适当的行动,如上所述。控制层次结构的演示者可能还会向服务器发送一条消息,以确保已删除的文件确实被删除。这种设置允许模块通过侦听从事件总线激发的事件来对其他模块中的事件做出反应。几乎没有发生耦合,并且交换视图、演示者或处理程序是完全无痛的。