Java設計模式之-觀察者模式(Observer)
如果你發現一件心儀的商品,但是由于雙十一已經剁手了,只能等到打折再去買。為了及時獲得該商品的打折信息,你有兩種方式:
- “拉”:每過一會兒就到店里看一下,有沒有相關的打折信息,把信息從店家“拉”到自己眼前;
- “推”:店家通過短信、郵件等方式將打折信息直接推送到自己眼前,當然你需要先告訴商家我要訂閱這些信息(垃圾短信另當別論)
上面兩種方式相比,我當然希望“推”模式,不用自己跑腿,只要和對方建立起信息變更的訂閱關系,就可以被動的收到通知,這就是我們今天要說的觀察者模式。(P.S. 傳統意義上,觀察者可以做成“推”或“拉”,但實際都是“推”,只是推的信息的多少)
觀察者模式
觀察者模式有5個模塊構成:
- 抽象被觀察者:定義了被觀察者的抽象方法,比如添加觀察者,移除觀察者,通知觀察者等;
- 具體被觀察者:實現了抽象被觀察者的方法,不一定是extends或implements,也可能是包含了一個專門用于管理觀察者的實例(如PropertyChangeSupport);
- 抽象觀察者:定義了觀察者需要實現的方法,該方法是與被觀察者約定好的,統一接口的,表示在接收到被觀察者推來的信息后應該怎樣反饋;
- 具體觀察者:實現了抽象觀察者的方法,定義了具體針對通知的響應行為,一般來說會影響客戶端的表現;
- 客戶端:直接操作的是抽象被觀察者接口,使被觀察者改變,并獲得反饋。
整個觀察者模式如上所說,現在我們來看一下具體如何實現。
抽象被觀察者
在Java中,抽象被觀察者可以用一個普通類代替,其中保存一個數組或鏈表用于存放需要訂閱變更的抽象觀察者。這種模式比較原始,既然用了Java,最好是不要再重造輪子了,所以我們直接來看一下Java中的一個輪子-PropertyChangeSupport
很多文章中其實提到的是Observable,我學習的時候也是想用這個,但是@Deprecated(since="9") :
* @deprecated
* This class and the {@link Observer} interface have been deprecated.
* The event model supported by {@code Observer} and {@code Observable}
* is quite limited, the order of notifications delivered by
* {@code Observable} is unspecified, and state changes are not in
* one-for-one correspondence with notifications.
* For a richer event model, consider using the
* {@link java.beans} package. For reliable and ordered
* messaging among threads, consider using one of the concurrent data
* structures in the {@link java.util.concurrent} package.
* For reactive streams style programming, see the
* {@link java.util.concurrent.Flow} API.
*/
@Deprecated(since="9")
public class Observable {
大意就是因為Observable局限性較大,通知的發送順序不可預知,且狀態的改變也不是與通知一一對應的。而它提到的一種替代則是java.beans.PropertyChangeSupport:
其中主要的方法是
- 構造函數
- addPropertyChangeListener,增加觀察者
- firePropertyChange,通知觀察者
- removePropertyChangeListener,移除觀察者
具體被觀察者
一個PropertyChangeSupport已經能夠作為一個抽象被觀察者使用了,現在我們再寫一個具體被觀察者MyEntity:
public class MyEntity {
private int visitCount;
private PropertyChangeSupport observer = new PropertyChangeSupport(this);
public void addObservers(PropertyChangeListener ob) {
observer.addPropertyChangeListener(ob);
}
public void addObservers(String propertyName, PropertyChangeListener ob) {
observer.addPropertyChangeListener(propertyName, ob);
}
public void visit() {
++visitCount;
observer.firePropertyChange("visitCount", visitCount - 1, visitCount);
}
}
MyEntity有一個visitCount屬性,當visit()被調用時,該屬性值加1,而后調用firePropertyChange通知所有已經訂閱的PropertyChangeListener。可以看到我寫了兩個addObservers,一個有propertyName參數,另一個沒有。
第一個addObservers(PropertyChangeListener ob)
會通知所有的ob對象,而第二個addObservers(String propertyName, PropertyChangeListener ob)
則是在firePropertyChange()
的第一個參數與propertyName一致時才被通知到。
抽象觀察者
其實在上面addObservers中的PropertyChangeListener就是一個抽象觀察者,它其實是一個定義了變更響應方法propertyChange的接口:
/**
* A "PropertyChange" event gets fired whenever a bean changes a "bound"
* property. You can register a PropertyChangeListener with a source
* bean so as to be notified of any bound property updates.
* @since 1.1
*/
public interface PropertyChangeListener extends java.util.EventListener {
/**
* This method gets called when a bound property is changed.
* @param evt A PropertyChangeEvent object describing the event source
* and the property that has changed.
*/
void propertyChange(PropertyChangeEvent evt);
}
而如果我們一直翻看firePropertyChange方法的底層代碼,最后可以發現這么一個函數,它循環調用所有listener的propertyChange方法:
private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
if (listeners != null) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(event);
}
}
}
具體觀察者
具體觀察者通過實現PropertyChangeListener接口,來定義propertyChange具體應該做什么。我們可以單獨建一個類來做,但更常見的做法是使用匿名內部類:
MyEntity entity = new MyEntity();
entity.addObservers(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Here we comes a change event!");
System.out.println(evt.toString());
}
});
測試使用
其實就是一個簡單的main函數,首先添加兩個Observer,分別使用和不使用帶有propertyName參數的函數,最后調用visit()使變更發生:
public class ObserverTest {
public static void main(String[] args) {
MyEntity entity = new MyEntity();
entity.addObservers(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Here we comes a change event!");
System.out.println(evt.toString());
}
});
entity.addObservers("visitCount", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Change Event with property Name");
}
});
entity.visit();
}
}
該main函數執行結果如下:
Here we comes a change event!
java.beans.PropertyChangeEvent[propertyName=visitCount; oldValue=0; newValue=1; propagationId=null; source=com.mock.observers.MyEntity@77f03bb1]
Change Event with property Name