由於實在不喜歡 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 是因為 ... 我認為它在這個問題上幫不上什麼忙。利用不定引數則是為了消去要為不同的參數組而另訂類別的困擾。
沒有留言:
張貼留言