xiaorulou 2011-10-13
绝大部分开发人员都清楚MVC结构:
M - model:代表数据模型
V - view:代表显示层
C - controller:代表逻辑控制层
这种结构降低了M-V-C之间的耦合性,使得模型可以独立于显示层和逻辑层的实现,即系统从struts改到struts 2的时候模型不需要做过多的更改;也使得显示层可以专注于显示,不需要对逻辑有任何了解或者参与,这样的好处是可以让前台开发人员专注于前台,提高整个开发组的工作效率和合作度。
随着系统复杂度的增加,多个controller之间需要交互(调用服务等),原本清晰的架构也会随之变得相当繁杂。
这时候就需要event bus的概念来解耦合不同的MVC系统之间的联系。
以GWT的event bus为例,在GWT的文档中对于EventBus是这样定义的:
public abstract class EventBusextends java.lang.Objectimplements HasHandlersDispatches GwtEvents to interested parties. Eases decoupling by allowing objects to interact without having direct dependencies upon one another, and without requiring event sources to deal with maintaining handler lists. There will typically be one EventBus per application, broadcasting events that may be of general interest.
EventBus的直接功能主要是两条:1,传递events;2,关注这些events以及他们的子events。
一般来说每个application定义一个EventBus,在GWT世界中,EventBus是定义于HandlerManager之上的。因此,不能把整个application里面所有涉及到的event都注册到EventBus,原则是,只有application层面关心的events,比如用户切换了视图(view),用户需要更新视图的数据,用户要保存视图上的数据等等,而诸如用户点击了一个按钮,或者用户关掉了视图,这些显示层就能解决的需求,它们的event就定义在视图里,也由视图来关注并加以处理即可。
下面举一个GWT项目中应用EventBus的小例子(需要一些GWT的基本知识)。
笔者先创建了一个叫game.pingpong的GWT项目,这个项目含有两个View,将展示如果通过eventBus来使得不同的View之间不需要通过不同的controller来交互,而是通过eventBus这样一个十分清晰的逻辑结构来进行互动。
Game_pingpong.html中定义了3个div标签,一个button居中,左右两边各准备一个label,用来模拟乒乓球中两边选手的接球(左边选手碰球的时候左边显示Ping,右边不显示;右边选手碰球的时候右边显示Pang,左边不显示)。
<h1>PingPong Game</h1> <div id="button" align="center"></div> <br><br><br> <div id="ping" align="left" ></div> <div id="pong" align="right"></div>
先定义PingHandler和PongHandler这两个接口
package game.pingpong.events;import com.google.gwt.event.shared.EventHandler;public interface PingEventHandler extends EventHandler { void onEvent(PingEvent event);}package game.pingpong.events;import com.google.gwt.event.shared.EventHandler;public interface PongEventHandler extends EventHandler { void onEvent(PongEvent event);}
接下来定义PingEvent和PongEvent
package game.pingpong.events;import com.google.gwt.event.shared.GwtEvent;public class PingEvent extends GwtEvent<PingEventHandler> { public static Type<PingEventHandler> TYPE = new Type<PingEventHandler>(); @Override public com.google.gwt.event.shared.GwtEvent.Type<PingEventHandler> getAssociatedType() { return TYPE; } @Override protected void dispatch(PingEventHandler handler) { handler.onEvent(this); }}package game.pingpong.events;import com.google.gwt.event.shared.GwtEvent;public class PongEvent extends GwtEvent<PongEventHandler> { public static Type<PongEventHandler> TYPE = new Type<PongEventHandler>(); @Override public com.google.gwt.event.shared.GwtEvent.Type<PongEventHandler> getAssociatedType() { return TYPE; } @Override protected void dispatch(PongEventHandler handler) { handler.onEvent(this); }}Game_pingpong类现在是这样的
package game.pingpong.client;import game.pingpong.view.PingView;import game.pingpong.view.PongView;import com.google.gwt.core.client.EntryPoint;import com.google.gwt.event.shared.SimpleEventBus;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.RootPanel;/** * Entry point classes define <code>onModuleLoad()</code>. */public class Game_pingpong implements EntryPoint { /** * This is the entry point method. */ public void onModuleLoad() { final SimpleEventBus bus = new SimpleEventBus(); RootPanel.get("ping").add(new PingView()); RootPanel.get("pong").add(new PongView()); final Button button = new Button("Serve!"); RootPanel.get("button").add(button); }}在笔者这个项目的设计中,只要用户点击button是整个application的触发点,所以显然要给button加一个ClickHandler,在其中调用EventBus的fireEvent方法把PingEvent先fire出去(装上bus),这样项目中任何部件,只要关注了PingEvent(或者PingEvent的子类),就会接收到PingEvent已经被触发这个消息,并调用该部件中重写的PingEventHandler中的onEvent方法。
同时,构造PingView和PongView的时候需要传入EventBus。
package game.pingpong.client;import game.pingpong.events.PingEvent;import game.pingpong.view.PingView;import game.pingpong.view.PongView;import com.google.gwt.core.client.EntryPoint;import com.google.gwt.event.dom.client.ClickEvent;import com.google.gwt.event.dom.client.ClickHandler;import com.google.gwt.event.shared.SimpleEventBus;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.RootPanel;/** * Entry point classes define <code>onModuleLoad()</code>. */public class Game_pingpong implements EntryPoint { /** * This is the entry point method. */ public void onModuleLoad() { final SimpleEventBus bus = new SimpleEventBus(); RootPanel.get("ping").add(new PingView(bus)); RootPanel.get("pong").add(new PongView(bus)); final Button button = new Button("Serve!"); button.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { bus.fireEvent(new PingEvent()); } }); RootPanel.get("button").add(button); }}
package game.pingpong.view;import game.pingpong.events.PingEvent;import game.pingpong.events.PingEventHandler;import game.pingpong.events.PongEvent;import com.google.gwt.event.shared.SimpleEventBus;import com.google.gwt.user.client.Timer;import com.google.gwt.user.client.ui.Label;public class PingView extends Label { public PingView(final SimpleEventBus bus) { bus.addHandler(PingEvent.TYPE, new PingEventHandler() { @Override public void onEvent(PingEvent event) { setText("Ping"); new Timer() { @Override public void run() { setText(""); bus.fireEvent(new PongEvent()); } }.schedule(1000); } }); }}
packagegame.pingpong.view;importgame.pingpong.events.PingEvent;importgame.pingpong.events.PongEvent;importgame.pingpong.events.PongEventHandler;importcom.google.gwt.event.shared.SimpleEventBus;importcom.google.gwt.user.client.Timer;importcom.google.gwt.user.client.ui.Label;publicclassPongViewextendsLabel{publicPongView(finalSimpleEventBusbus){bus.addHandler(PongEvent.TYPE,newPongEventHandler(){@OverridepublicvoidonEvent(PongEventevent){setText("Pong");newTimer(){@Overridepublicvoidrun(){setText("");bus.fireEvent(newPingEvent());}}.schedule(1000);}});}}
逻辑是当用户点击button时,eventBus会fire一个PingEvent。PingView关注了PingEvent,这样PingView里面调用的PingEventHandler中的onEvent就会发生作用,在视图中把ping这个label的内容设置成“Ping”,并运用GWT的Timer,过1秒后把内容清空,并通过eventBus fire一个PongEvent。同样,PongView关注了PongEvent,它完成一系列类似的活动之后,通过eventBus fire一个PingEvent。。。如此反复。