還記得警匪片中,強盜團夥如何配合作案嗎?幾個人在室內行竊,總有一兩個人在門口「放風」。一旦有風吹草動,放風者會立即通知裡面的夥伴緊急撤退。值得注意的是,放風者不一定認識裡面的每一個夥伴,新來的小弟可能也不認識放風者,但這不影響他們之間的溝通,因為他們之間有預先約定好的暗號。這正是觀察者模式在現實生活中的生動寫照。
觀察者模式又稱發布-訂閱模式。GoF 給出的定義是:定義對象之間的一種一對多依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都會得到通知並自動更新。
在深入之前,先回顧一個面向對象設計的核心原則——單一職責原則。系統中的每個對象應專注於領域中的單一職責,理想情況下一個對象只做一件事。這帶來了復用性、可維護性,也是重構的良好基礎。幾乎所有設計模式都基於此原則。觀察者模式的起源常被認為在 GUI 與業務數據的分離上,但其應用遠不止於此。
以當下火熱的微信服務號為背景:
核心角色包括:Subject(主題接口)、Observer(觀察者接口)、ConcreteSubject(具體主題)、ConcreteObserver(具體觀察者)。
package com.zhy.pattern.observer;
/** 主題接口,所有主題必須實現 */
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
package com.zhy.pattern.observer;
/** 觀察者接口 */
public interface Observer {
void update(String msg);
}
package com.zhy.pattern.observer;
import java.util.ArrayList;
import java.util.List;
public class ObjectFor3D implements Subject {
private List<Observer> observers = new ArrayList<>();
private String msg; // 3D 號碼
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if (index >= 0) observers.remove(index);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(msg);
}
}
/** 主題更新消息 */
public void setMsg(String msg) {
this.msg = msg;
notifyObservers();
}
}
package com.zhy.pattern.observer;
public class Observer1 implements Observer {
private Subject subject;
public Observer1(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void update(String msg) {
System.out.println("observer1 獲得 3D 號碼 -->" + msg + ", 我要記下來。");
}
}
package com.zhy.pattern.observer;
public class Observer2 implements Observer {
private Subject subject;
public Observer2(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void update(String msg) {
System.out.println("observer2 獲得 3D 號碼 -->" + msg + "我要告訴室友們。");
}
}
package com.zhy.pattern.observer.test;
import com.zhy.pattern.observer.*;
public class Test {
public static void main(String[] args) {
ObjectFor3D subjectFor3d = new ObjectFor3D();
Observer observer1 = new Observer1(subjectFor3d);
Observer observer2 = new Observer2(subjectFor3d);
subjectFor3d.setMsg("20140420的3D號碼是:127");
subjectFor3d.setMsg("20140421的3D號碼是:333");
}
}
observer1 獲得 3D 號碼 -->20140420的3D號碼是:127, 我要記下來。 observer2 獲得 3D 號碼 -->20140420的3D號碼是:127我要告訴室友們。 observer1 獲得 3D 號碼 -->20140421的3D號碼是:333, 我要記下來。 observer2 獲得 3D 號碼 -->20140421的3D號碼是:333我要告訴室友們。
整個架構呈現鬆耦合:主題不依賴觀察者實現,新增觀察者無需修改主題代碼;觀察者如何處理數據與主題無關。
Java 提供 java.util.Observable(類)與 java.util.Observer(接口)實現觀察者模式,代碼更簡潔。
package com.zhy.pattern.observer.java;
import java.util.Observable;
public class SubjectFor3d extends Observable {
private String msg;
public String getMsg() { return msg; }
public void setMsg(String msg) {
this.msg = msg;
setChanged();
notifyObservers();
}
}
public class SubjectForSSQ extends Observable {
private String msg;
public String getMsg() { return msg; }
public void setMsg(String msg) {
this.msg = msg;
setChanged();
notifyObservers();
}
}
package com.zhy.pattern.observer.java;
import java.util.Observable;
import java.util.Observer;
public class Observer1 implements Observer {
public void registerSubject(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof SubjectFor3d) {
System.out.println("subjectFor3d's msg -- >" + ((SubjectFor3d)o).getMsg());
} else if (o instanceof SubjectForSSQ) {
System.out.println("subjectForSSQ's msg -- >" + ((SubjectForSSQ)o).getMsg());
}
}
}
public class Test {
public static void main(String[] args) {
SubjectFor3d subjectFor3d = new SubjectFor3d();
SubjectForSSQ subjectForSSQ = new SubjectForSSQ();
Observer1 observer1 = new Observer1();
observer1.registerSubject(subjectFor3d);
observer1.registerSubject(subjectForSSQ);
subjectFor3d.setMsg("hello 3d'nums : 110 ");
subjectForSSQ.setMsg("ssq'nums : 12,13,31,5,4,3 15");
}
}
subjectFor3d's msg -- >hello 3d'nums : 110 subjectForSSQ's msg -- >ssq'nums : 12,13,31,5,4,3 15
JDK 將 addObserver、removeObserver、notifyObservers 實現在 Observable 類中,而非接口。雖然違反「面向接口編程」,但避免了每個主題重複實現這三個方法,實現了代碼復用。若用接口則無法復用方法實現,也難以用組合方式復用。因此這種設計是合理的權衡。
觀察者模式實現了主題與觀察者的解耦,適用於一對多的通知場景。無論自定義實現還是使用 JDK 內置類,核心思想一致:發布者不關心誰訂閱,訂閱者只關心感興趣的事件。掌握此模式,能靈活應對事件驅動、消息推送等業務場景。