觀察者模式又稱為發(fā)布訂閱模式。一個發(fā)布者對應多個訂閱者,一旦發(fā)布者的狀態(tài)發(fā)生改變時,訂閱者將收到訂閱事件。本文中涉及的代碼請點擊這里。
先看看一個生活中的例子:
當我們想訂一份報紙,我們先去郵局找到報紙的編號后填寫訂閱單并繳費。當報社有新報紙發(fā)出時,郵局會將我們訂閱的報紙發(fā)給我們。
為了簡單我們去掉郵局環(huán)節(jié)簡化成:報社有新報紙后馬上通知用戶,這就是觀察者。
定義對象間的一對多關系,當一個對象的狀態(tài)發(fā)生變化時,所依賴于它的對象都得到通知并主動更新。在觀察者模式中,多個訂閱者成為觀察者(Observer),被觀察的對象成為目標(Subject)。觀察者的UML模型如下:

先定義Subject并寫一個ConcreteSubject繼承Subject:
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();
public void attach(Observer observer){
observers.add(observer);
}
public void detach(Observer observer){
observers.remove(observer);
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
public class ConcreteSubject extends Subject{
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
notifyObservers();
}
}
再定義一個接口Observer,并寫一個ConcreteObserver實現(xiàn)Observer接口:
interface Observer {
public void update(Subject subject);
}
public class ConcreteObserver implements Observer{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Subject subject) {
System.out.println(name + "狀態(tài):" + ((ConcreteSubject)subject).getSubjectState());
}
}
最后看看主函數(shù)方法:
public class Client2 {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver();
observer1.setName("張三");
ConcreteObserver observer2 = new ConcreteObserver();
observer2.setName("李四");
ConcreteObserver observer3 = new ConcreteObserver();
observer3.setName("王二");
subject.attach(observer1);
subject.attach(observer2);
subject.attach(observer3);
subject.setSubjectState("看完報紙");
}
}
打印出來的結果:
張三狀態(tài):看完報紙
李四狀態(tài):看完報紙
王二狀態(tài):看完報紙
在實現(xiàn)觀察者模式的時候,一定要注意觸發(fā)通知的時機。一般情況下是在完成了狀態(tài)改變之后觸發(fā),因為通知會傳遞數(shù)據(jù),比如在setSubjectState時先通知觀測者就會發(fā)生錯誤。
// 錯誤寫法
public void setSubjectState(String subjectState) {
notifyObservers(); // 通知應該放在狀態(tài)改變之后,因為 update(Subject subject) 中的參數(shù)類型為Subject
this.subjectState = subjectState;
}
在觀察者模式的實現(xiàn)上,有推模式和拉模式兩種方式:
- 推模式
Subject主動向Observer推送消息,不管對方是否需要,推送的信息通常是目標對象的全部或部分數(shù)據(jù),相當于廣播通信。 - 拉模型
Subject在通知Observer時只傳遞少量信息,如果觀察者需要更具體的信息,再由Observer主動去拉取數(shù)據(jù)。這樣的模型實現(xiàn)中會把Subject自身通過update方法傳入到Observer。
當前上面的實現(xiàn)使用的就是拉模型。通過(ConcreteSubject)subject得到具體對象,獲得信息。
當然Java本身就有觀察者模式的部分實現(xiàn),分別是java.util.Observable java.util.Observable。
下面看一個使用Java自帶觀察者模式的例子:
新的目標直接繼承Java中定義的Observerable:
public class NewsPaperObservable extends Observable{
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
setChanged(); // 必須調用這個方法來通知Observer狀態(tài)發(fā)生了改變
notifyObservers(content);
}
}
新的觀察者也直接實現(xiàn)Observer接口:
public class ReaderObserver implements Observer{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + "讀取推送信息" + arg);
System.out.println(name + "讀取拉取信息" + ((NewsPaperObservable)o).getContent());
}
}
主函數(shù)和前面的相似:
public class Client {
public static void main(String[] args) {
NewsPaperObservable subject = new NewsPaperObservable();
ReaderObserver reader1 = new ReaderObserver();
reader1.setName("張三");
ReaderObserver reader2 = new ReaderObserver();
reader2.setName("李四");
ReaderObserver reader3 = new ReaderObserver();
reader3.setName("王二");
subject.addObserver(reader1);
subject.addObserver(reader2);
subject.addObserver(reader3);
subject.setContent("粗大事啦啦啦");
}
}
打印出結果:
王二讀取推送信息粗大事啦啦啦
王二讀取拉取信息粗大事啦啦啦
李四讀取推送信息粗大事啦啦啦
李四讀取拉取信息粗大事啦啦啦
張三讀取推送信息粗大事啦啦啦
張三讀取拉取信息粗大事啦啦啦
使用Java自帶的觀察者模式需要注意以下幾個問題:
- 具體的目標實現(xiàn)中不需要再維護觀察者的注冊信息了,這個在Java中的Observable類中實現(xiàn)了。
- 觸發(fā)通知方式有些變化,需要先調用
setChanged方法。 - 具體的觀察者中,update()方法其實能同時支持推模型和拉模型。