網頁

2009年12月20日 星期日

[GWT] 利用 HandlerManager 實作共用的 Event Bus

GWT 從版本 1.6 開始提供了新的 Event Model - Handler,並捨棄了原先的 Listener 方式。

關於 Listener 與 Handler 的差別,我推荐看這篇文章: GWT’s new Event Model – Handlers in GWT 1.6 [1]。

在這篇文章中,我將兩者與本篇文章有關的部份做介紹:


Listener:
  1. 對於每一個 Event Source 會有一個對應的 Listener,例如 MouseListener, KeyboardListener...等。
  2. 在每一個 Widget 中,針對每一種 Event Source 的 Listener,都會用一個獨立的 ListenerCollection 去紀錄。
Handler:
  1. 每一個 Event Type 都會有一個對應的 Handler,因此原先 MouseListener 相關的事件,將會被重新區分成 MouseDownHandler, MouseUpHandler...等。這樣做的一個額外的好處是,當只需要針對一種 Event Type 做處理時,實作 Handler 的類別只需要 over-ridding 一個對應的 method,而不需要像寫 Listener 時,仍然需要 over-ridding 其他沒用到的 method。
  2. 每一個 Widget 中,都只會有一個 HandlerManager,負責紀錄所有的 Event Handler。

由於 HandlerManager 能夠針對不同的 Event,dispatch 其對應的 Handler 去處理,因此我們也可以利用 HandlerManager 來做為整個 Web App 共用的 Event Bus。[2], [3]


先說明一下本篇文章欲使用的例子:
在一個 Web App 上,有許多的 Panel,我們現在希望當按下 A Panel 上的 Button 時,B Panel 上的 TextBox 會輸出文字。

在原本的做法裡,我們會讓 A panel 實作 ClickHandler,當 A panel 收到事件時,就去呼叫 B panel 所提供的方法。

這個作法會使得 A panel 必須持有 B panel 的 reference(就 UML 的角度來說就是 A 對 B 有 Aggregation 關係),這樣會使得 A, B 之間的耦合度高,造成未來擴充、修改的困難。

接下來就是解決辦法啦!

  1. 首先,於 EntryPoint 建立 HandlerManager 物件,並且將 Reference 傳給所有的 sub-component,這個部份可以利用 Constructor 達成。
    HandlerManager eventBus = new HandlerManager(null); 
  2. 接下來為每一個欲處理的事件寫一組 Event / Handler
    public class FooEvent extends GwtEvent<FooEvent.FooHandler> {
    
        public interface FooHandler extends EventHandler{
            void onFoo(FooEvent event);
        }
    
        public static final GwtEvent.Type<FooHandler> TYPE =
                new GwtEvent.Type<FooHandler>();
    
        @Override
        protected void dispatch(FooHandler handler) {
            handler.onFoo(this);
        }
    
        @Override
        public GwtEvent.Type<FooHandler> getAssociatedType() {
            return TYPE;
        }
    }
    
  3. 然後在 B panel 裡頭寫上事件處理,並且向 Event Bus 註冊。
    @Override
    protected void onLoad(){
        // register event handler
        fooRegistration = eventBus.addHandler(FooEvent.TYPE, new FooHandler(){
            @Override
            public void onFoo(FooEvent event) {
                textBox.setText("A panel's button click!");
            }
        });
    }
    
    @Override
    protected void onUnload(){
        // unregister event handler
        fooRegistration.removeHandler();
    }
    
  4. 最後是 A panel 的事件觸發,當按下按鈕時:
    eventBus.fireEvent(new FooEvent());
    

這樣一來,A 與 B 彼此可以完全不知道對方的存在,將可以大大的降低耦合度。

參考資料:
[1]: GWT’s new Event Model – Handlers in GWT 1.6
[2]: Google Web Toolkit Architecture: Best Practices For Architecting Your GWT App
[3]: GWT Event Bus Discussions
[4]: 1.6 版多了什麼新東西?

沒有留言:

張貼留言