0

在我們用java編程的遊戲中,我們經常使用觀察者。對於事件的傳播,我們有迴路像在觀察者模式下集中傳播事件

for(Observer o : observers) { 
    o.somethingHappened(); 
} 

它的工作原理,當然,但我們要記下每一個這種愚蠢的循環,每一個事件發生的事情:

for(Observer o : observers) { 
    o.somethingElseHappened(); 
} 

一段時間回來,我有同樣的事情在C++中,並可以使用成員函數指針,而不是寫如

dispatchEvent(&MyAbstractHandler::somethingHappened); //loop over observers for any "event" provided 

..這似乎是對我來說最好的解決方案。但是由於Java沒有這個能力,我想知道什麼是最好的解決方案。

您可以使用Event-Object並將其傳遞給一個方法handleEvent(),該方法對於幾個Event-inheritant而言是重載的,因此請使用多態性。但是這會給你一大堆空的類(爲了多態性我總是批評類)。還有反射,但這似乎不是一個典型的用例,也有性能問題。

那麼你對此有何看法?我錯過了一種方法嗎?

+0

爲什麼不使用PropertyChangeSupport/PropertyChangeListener,或者模擬它的作用? – 2011-12-16 20:50:35

回答

4

(從MMO發言開發者的視角)

我發現,爲了可伸縮性,您可能需要在多個線程中處理事件處理;我通常有這樣一個規律:

public class Event { EventType getType(); } 
// extend Event for special cases that require additional attributes… 

public class EventDispatcher { 
     Map<Class<? extends Event>, List<Observer>> observers; 
     void send (final Event e) { 
      for (Observer o : observers.get(e.getClass())) { 
        o.receiveEvent (e); 
      } 
     } 
    } 

或者,你可以使用一個enum

public enum EventType { … } ; 
    … 
    Map <EventType,List<Observer>> observers; 
    … 
     for (Observer o : observers.get(e.getEventType())) … 

當你成長到處理更多的觀察員,您可能需要分區的事件處理和/或者讓它在其他線程中運行。 EventDispatcher.send(Event)方法爲您提供了一個擴展該方法的地方,例如放入空間分區(由於距離太遠無法檢測到此事件),或者將Event對象注入隊列以便在其他線程上進行異步處理,其中事件處理程序不會影響產生事件的線程。

我們最初開始使用這個模型來處理事件傳播回人類玩家(所以網絡代碼在自己的線程上同步運行),但是發現隊列系統也幫助加快了AI和物理傳播。

可能的缺點是你必須在你的Observer類上有一個switch/case過濾器。這通常屬於一個普通的基類,假設你有相當少量的EventType枚舉。在早期的Romance MMO內核迭代中(爲了向後兼容,仍然支持1.1代碼庫),我們使用接口來接收各種事件。 (基類掛鉤到這些一般的事件接收器 - 我們稱之爲行動從UI事件處理程序來區分)

public interface EventFooHandler { 
     public void handleEventFoo (int bar, int baz); 
}; 
public class SomeObject implements EventFooHandler … 

也許這些接口是你的意思這一般是「噸空類的?」 「通俗的Java正確的方式來做它」的東西,在你自己的堆棧/線程中運行;例如我相信Swing以這種方式大量使用「回調函數」,就像任何其他標準Java庫一樣。

當然,如果你不想簡單地實現每個觀察接口,您可以使用匿名類...

getEventDispatcher().registerForEventFoo 
     (new EventFooHandler { 
       public void handleEventFoo (int bar, int baz) { 
         this.doGribble (bar + 2, baz); 
      }}); 

如果你不咬咬牙,潛入實體系統(我肯定有人肯定會彈出並插入一個,可能會提到不可變狀態的樂趣,他們建議轉換到Erlang爲您的下一個項目50%的可能性:-)),我傾向於類似於前者。 「統一事件」系統允許「管道」是正交的,事件→觀察者路由在一個地方被改變,並且隨着時間的推移,不會在觀察者上傳播每種事件類型的雜項方法。

1

在Java中,您有事件監聽器,可以在需要時觸發,例如我有一個程序需要在每次訂單修改時更新Order視圖。所以我設置了一個PropertyChangeListener,以便每當某些屬性發生變化時(即transactionsList),它將觸發一個方法來更新訂單的JTable。

雖然使用它們時必須小心,以避免出現併發問題。

下面是一些示例代碼:從Order類

public Order makeNewOrder() { 
    currentOrder = new Order(); 
    currentOrder.addPropertyChangeListener(new java.beans.PropertyChangeListener() { 
     public void propertyChange(java.beans.PropertyChangeEvent evt) { 
      updateOrderTable(); 
     } 
    }); 
    updateOrderTable(); 
    return currentOrder; 
} 

提取物:

public class Order implements Serializable { 
... 
transient private PropertyChangeSupport propertyChangeSupport 
    = new PropertyChangeSupport(this); 
... 
    public void addTL(...) { 
    TransactionLine t = new TransactionLine(...); 
    transactions.add(t); 
    if (propertyChangeSupport != null) 
     propertyChangeSupport.firePropertyChange("transactions", false, true); 
    } 
//each instance should add this when created 
public void addPropertyChangeListener(PropertyChangeListener listener) { 
    propertyChangeSupport.addPropertyChangeListener(listener); 
} 

這是被調用的方法:

/** This method is fired on propertyChange */ 
public void updateOrderTable() { 
    OrderTable ot = new OrderTable(currentOrder); 
    currentOrderTable.setModel(ot.getTableModel()); 
    currentOrderTable.setColumnModel(ot.getColumnModel()); 
    savedOrdersSelector.setModel(new javax.swing.DefaultComboBoxModel(getSavedOrders())); 
    OrderSummary.setOrder(currentOrder); 
}