觀察者模式 Observer Pattern

1412 字
7 分鐘
觀察者模式 Observer Pattern

目的#

定義一對多的物件依存關係,讓物件狀態一有變動就自動通知其他相依物件做該做的更新動作。

適用#

  • 當一個抽象觀念有兩個層面,且其中一方會依賴另一方時。可將它們分別置於兩種物件上,以便於個別改變及再利用。某一物件有變化時,其他物件也得跟著改變,但事先又不知道後者到底有多少時。當物件必須能夠通知其他物件,但又不能假設後者(其他物件)是誰;換句話說,不希望這些物件之間連結得過於緊密時。

結構及成員#

Collaborations: 當ConcreteSubject產生足以讓Observer的狀態不再一致時,就會主動通知所有該通知的Observer。 當ConcreteObserver收到通知後,可向Subject洽詢資訊,利用取回的資訊同步化自己的狀態

※如下方Sequence Diagram所示,ConcreteObserver物件會將自我更新的動作延後到aConcreteSubject通知後才執行。且Notify()不一定得由Observer發動,也可以由Subject或其他物件發動。

  • Subject: like the JComponent認得Observer。任何數量的Observer物件均可訂閱此Subject。提供增刪Observer物件的介面。Observer: like the ActionListener制訂自我更新的介面。在Subject有變時藉以通知自己該隨之改變。ConcreteSubject: like the JButton儲存ConcreteSubject物件感興趣的狀態。狀態有變時會通知它的Observer。ConcreteObserver: like the ButtonActionListener持有一個ConcreteSubject。儲存應該要和Subject保持一致的狀態。實作出Observer的自我更新介面,確保與Subject的狀態維持同步。

影響結果#

好處#

  • Subject與Observer之間屬於抽象耦合。因為Subject只知道有一堆Observer物件,但不知道他們的具體類別是誰。支援廣播功能。

壞處#

  • 不預期的更新。由於Observer們互不知道彼此存在,所以可能改變到Subject會有連鎖反應,所以要妥善制訂或維護相依關係。

實作#

將Subject對應到Observer#

  • Observer們儲存在Subject裡,耗空間,速度快。Observer們儲存在雜湊表裡,省空間,速度慢。

訂閱一個以上的Subject#

擴充Update()介面,讓Observer知道究竟是哪一個Subject送來訊息,也可以讓Subject把自己當參數傳給Update()

誰負責觸發更新程序?#

  • 由Subject的狀態設定方法,在改變Subject狀態後呼叫Notify()。優點:Client不用自己呼叫Notify()。缺點:如果有一連串的狀態設定動作,會引發一連串的Notify(),沒有效率。由Client在適當時間呼叫Notify()。優點:一連串狀態都改變好再呼叫Notify()。缺點:容易出錯。

仍指涉至已刪除的Subject#

當Subject刪除後,應主動通知Observer刪除Subject的Reference。

確保Subject的狀態在知會他人之前就已經是完好無缺的#

  • 因為更新過程中,Observer會跑來洽詢Subject的目前狀態。如:子類別呼叫父類別的方法時,很容易違反這項規定。可以用template method提供子類別覆寫Subject所提供的基礎操作,並在最後一個步驟才呼叫Notify()。

避免與特定Observer相關的更新協定:Push及Pull模型#

  • 實作時,通常會讓Subject多廣播一些客外的資訊,資訊多寡有很大的彈性。Push Model(推播模型):不管Observer要不要,Subject都會送出最詳盡的資訊。(Subject或多或少知道Observer的個別需求)Pull Model(拉力模型):Subject只送出最少量的資訊,不夠的話,Observer再自己跟Subject取得細節。(Subject不知道Observer的個別需求)

明確指定感興趣的變動#

  • 擴充Subject的登記介面,讓Observer只登記感興趣的事件。有事件發生時,只通知有關的Observer們。

封裝起複雜的更新語意#

  • 若Subject和Observer之間的依存關係過於繁雜,最好另設一個ChangeManager物件來管理。(Mediator Pattern)例如:某個操作會動到數個Subject,可能要確保直到所有Subject都已更改完成之後才去通知Observer,以避免重複通知好幾次。ChangeManager的任務: 將Subject對應到Observer,提供管理這種對應關係的介面,減輕Subject和Observer的負擔。定義特殊的更新策略。受Subject之託,更新所有相依的Observer。

Example: Archiver#

Interface: Subject
package archiver;
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
Class: Database
package archiver;
import java.util.*;
public class Database implements Subject{
private Vector observers;
private String operation;
private String record;
public Database() {
observers = new Vector();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
public void editRecord(String operation, String record) {
this.operation = operation;
this.record = record;
notifyObserver();
}
public void notifyObserver() {
for (int loopIndex = 0; loopIndex < observers.size(); loopIndex++) {
Observer observer = (Observer)observers.get(loopIndex);
observer.update(operation, record);
}
}
}
Interface: Observer
package archiver;
public interface Observer {
public void update(String operation, String record);
}
Class: Archiver
package archiver;
public class Archiver implements Observer{
public Archiver() {
}
public void update(String operation, String record) {
System.out.println("This archiver says a " + operation +
" operation was performed on " + record);
}
}
Class: Client
package archiver;
public class Client implements Observer {
public Client() {
}
public void update(String operation, String record) {
System.out.println("This client says a " + operation
+ " operation was performed on " + record);
}
}
Class: Boss
package archiver;
public class Boss implements Observer {
public Boss() {
}
public void update(String operation, String record) {
System.out.println("This Boss says a " + operation +
" operation was performed on " + record);
}
}
Class: TestObserver
package archiver;
public class TestObserver {
public static void main(String[] args) {
Database database = new Database();
Archiver archiver = new Archiver();
Client client = new Client();
Boss boss = new Boss();
database.registerObserver(archiver);
database.registerObserver(client);
database.registerObserver(boss);
database.editRecord("delete", "record 1");
}
}
Result:
This archiver says a delete operation was performed on record 1
This client says a delete operation was performed on record 1
This Boss says a delete operation was performed on record 1

文章分享

如果這篇文章對你有幫助,歡迎分享給更多人!

觀察者模式 Observer Pattern
https://linziyou.info/posts/2020-11-16-觀察者模式-observer-pattern/
作者
Lin Ziyou
發布於
2020-11-16
許可協議
CC BY-NC-SA 4.0
最後更新於 2020-11-16,距今已過 1932 天

部分內容可能已過時

Profile Image of the Author
Lin Ziyou
Hi! I'm Jerry~
分類
標籤
站點統計
文章
45
分類
8
標籤
10
總字數
43,470
運作天數
0
最後活動
0 天前

目錄