由於實在不喜歡 Swing 的 listener 方式,因此我嘗試使用 Java 內建的 Observer 和 Observable 來套用 Observer Pattern,但它比我想像中地還不靈活,原因有三:
- 必須要呼叫 Observable.setChanged 才會讓 Observable.notifyObservers 生效,而 Observable.setChanged 是 protected,這意味著你至少必須繼承 Observable 類別,這對於不允許多重繼承的 Java 來說很要命,因為 host class 很有可能來自另一繼承體系。
- 你沒辦法透過 composition 繞過這個限制,因為 Observer.update 帶有一個型態為 Observable 的參數,語義上這個參數必須要是發出事件的物件,但若是使用 composition 持有 Observable 物件,Observer 收到的參數就會跟真正發出事件的物件無關,這會破壞這組範式的假設。
- 你有可能需要在同一個類別裡發送不同的事件,但使用繼承的做法你只能使用一種通知。如果要達到分派不同事件的目的,就必須要在那唯一的一個 Object 參數上作手腳。
為此我參考了之前設計的 Observer Pattern 大綱,重新造了一個輪子:
using java.util.ArrayList; class Signal { public static interface Slot { void call( Object sender, Object ... args ); } public Signal( Object sender ) { this.sender_ = sender; this.slots_ = new ArrayList< Slot >(); } public synchronized Boolean connect( Slot slot ) { if( this.slots_.indexOf( slot ) >= 0 ) { return false; } this.slots_.add( slot ); return true; } public synchronized void disconnect() { this.slots_.clear(); } public synchronized Boolean disconnect( Slot slot ) { return this.slots_.remove( slot ); } public synchronized void emit( Object ... args ) { for( Slot slot : this.slots_ ) { slot.call( this.sender_, args ); } } private Object sender_; private ArrayList< Slot > slots_; }其實 Slot 沒必要是 Signal 的從屬類別,只是它也只會給 Signal 使用,就放在裡面了。使用上只要實作出 Slot 介面,再丢給 Signal.connect 就可以註冊,呼叫 Signal.emit 就可以發出通知:
class A { private Signal event1_; private Signal event2_; public A() { this.event1_ = new Signal( this ); this.event2_ = new Signal( this ); } public Signal onEvent1() { return this.event1_; } public Signal onEvent2() { return this.event2_; } public void trigger() { this.event1_.emit( "First event happened" ); this.event2_.emit( 2, "Multiple args" ); } } // ... client code A a = new A(); a.onEvent1().connect( new Signal.Slot() { void call( Object sender, Object ... args ) { System.err.printf( "$s message : %s", sender, args[0] ); } } ); a.onEvent2().connect( new Signal.Slot() { void call( Object sender, Object ... args ) { System.err.printf( "$s message : %d, %s", sender, args.length, args.[1] ); } } ); a.trigger();
沒有用上 generic 是因為 ... 我認為它在這個問題上幫不上什麼忙。利用不定引數則是為了消去要為不同的參數組而另訂類別的困擾。
沒有留言:
張貼留言