觀察者模式,又稱發布-訂閱模式,是一種行為型設計模式。它定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一主題對象。當主題對象的狀態發生變化時,會自動通知所有已註冊的觀察者,使其能夠做出相應的反應。這種模式廣泛應用於事件處理系統、消息推送、GUI 程式設計等場景。
以下展示一個支援多線程環境、觀察者可自主訂閱/取消訂閱的完整實現:
public interface Subject {
void register(Observer observer);
void remove(Observer observer);
void notifyObservers();
String getMessage();
}
public class MySubject implements Subject {
private final List<Observer> observers = new ArrayList<>();
private volatile boolean changed = false;
private String message;
private final Object mutex = new Object();
@Override
public void register(Observer observer) {
if (observer == null) throw new NullPointerException();
synchronized (mutex) {
if (!observers.contains(observer)) observers.add(observer);
}
}
@Override
public void remove(Observer observer) {
synchronized (mutex) {
observers.remove(observer);
}
}
@Override
public void notifyObservers() {
List<Observer> tempObservers;
synchronized (mutex) {
if (!changed) return;
tempObservers = new ArrayList<>(observers);
changed = false;
}
for (Observer obj : tempObservers) {
obj.update();
}
}
public void makeChanged(String message) {
System.out.println("主題發生變更: " + message);
this.message = message;
this.changed = true;
notifyObservers();
}
@Override
public String getMessage() {
return this.message;
}
}
public interface Observer {
void update();
void setSubject(Subject subject);
}
public class MyObserver implements Observer {
private Subject subject;
@Override
public void update() {
String msg = subject.getMessage();
System.out.println("觀察者收到通知: " + msg);
}
@Override
public void setSubject(Subject subject) {
this.subject = subject;
}
public void subscribe(String subjectName) {
SubjectManagement.getInstance().getSubject(subjectName).register(this);
}
public void cancelSubscribe(String subjectName) {
SubjectManagement.getInstance().getSubject(subjectName).remove(this);
}
}
public class SubjectManagement {
private final Map<String, Subject> subjectMap = new HashMap<>();
public void addSubject(String name, Subject subject) {
subjectMap.put(name, subject);
}
public Subject getSubject(String name) {
return subjectMap.get(name);
}
private SubjectManagement() {}
public static SubjectManagement getInstance() {
return SubjectManagementHolder.INSTANCE;
}
private static class SubjectManagementHolder {
static final SubjectManagement INSTANCE = new SubjectManagement();
}
}
public class ObserverTest {
private static MySubject writer;
@BeforeClass
public static void setUp() {
writer = new MySubject();
SubjectManagement.getInstance().addSubject("Linus", writer);
}
@Test
public void testObserver() {
MyObserver reader1 = new MyObserver();
MyObserver reader2 = new MyObserver();
MyObserver reader3 = new MyObserver();
reader1.setSubject(writer);
reader2.setSubject(writer);
reader3.setSubject(writer);
reader1.subscribe("Linus");
reader2.subscribe("Linus");
reader3.subscribe("Linus");
writer.makeChanged("發布新文章:Java 設計模式深度解析");
}
}
Servlet 規範中的 Listener 正是觀察者模式的另一種形式——事件驅動模型的典型應用。JDK 提供了統一的事件源 EventObject 和標記介面 EventListener。以 ServletRequestAttributeListener 為例,當請求屬性發生變化時,容器會遍歷所有註冊的監聽器並調用對應方法:
private void notifyAttributeAssigned(String name, Object value, Object oldValue) {
Object[] listeners = context.getApplicationEventListeners();
if (listeners == null || listeners.length == 0) return;
ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(...);
for (Object listener : listeners) {
if (listener instanceof ServletRequestAttributeListener) {
ServletRequestAttributeListener l = (ServletRequestAttributeListener) listener;
l.attributeAdded(event); // 或 attributeReplaced/Removed
}
}
}
Java 標準庫提供 java.util.Observable 和 java.util.Observer,可直接用於快速實現。以下以「臥底接收行動指令」為例:
public class Police extends Observable {
public void change(String time) {
this.time = time;
setChanged();
notifyObservers(time);
}
}
public class UndercoverA implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("臥底A收到指令,行動時間:" + arg);
}
}
public class Client {
public static void main(String[] args) {
List<Observer> list = Arrays.asList(new UndercoverA(), new UndercoverB());
Police police = new Police(list);
police.change("02:25");
police.change("01:05"); // 時間變更,自動通知所有觀察者
}
}