一、什麼是觀察者模式
觀察者一般可以看做是第三者,比如在學校上自習的時候,大家肯定都有過交頭接耳、各種玩耍的經歷,這時總會有一個“放風”的小夥伴,當老師即將出現時及時“通知”大家老師來了。再比如,拍賣會的時候,大家相互叫價,拍賣師會觀察最高標價,然後通知給其它競價者競價,這就是一個觀察者模式。
對於觀察者模式而言,肯定有觀察者和被觀察者之分。比如在一個目錄下建立一個檔案,這時系統會通知目錄管理器增加目錄,並通知磁碟減少空間,在這裡,檔案就是觀察者,目錄管理器和磁碟就是被觀察者。
觀察者模式(Observer),又叫釋出-訂閱模式(Publish/Subscribe),定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於它的物件都會得到通知並自動更新。UML結構圖如下:
其中,Subject類是主題,它把所有對觀察者物件的引用檔案存在了一個聚集裡,每個主題都可以有任何數量的觀察者。抽象主題提供了一個介面,可以增加和刪除觀察者物件;Observer類是抽象觀察者,為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己;ConcreteSubject類是具體主題,將有關狀態存入具體觀察者物件,在具體主題內部狀態改變時,給所有登記過的觀察者發出通知;ConcreteObserver是具體觀察者,實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態相協同。
1. 主題Subject
首先定義一個觀察者陣列,並實現增、刪及通知操作。它的職責很簡單,就是定義誰能觀察,誰不能觀察,用Vector是執行緒同步的,比較安全,也可以使用ArrayList,是執行緒非同步的,但不安全。
1 public class Subject { 2 3 //觀察者陣列 4 private Vector<Observer> oVector = new Vector<>(); 5 6 //增加一個觀察者 7 public void addObserver(Observer observer) { 8 this.oVector.add(observer); 9 } 10 11 //刪除一個觀察者 12 public void deleteObserver(Observer observer) { 13 this.oVector.remove(observer); 14 } 15 16 //通知所有觀察者 17 public void notifyObserver() { 18 for(Observer observer : this.oVector) { 19 observer.update(); 20 } 21 } 22 23 }
2. 抽象觀察者Observer
觀察者一般是一個介面,每一個實現該介面的實現類都是具體觀察者。
1 public interface Observer { 2 //更新 3 public void update(); 4 }
3. 具體主題
繼承Subject類,在這裡實現具體業務,在具體專案中,該類會有很多變種。
1 public class ConcreteSubject extends Subject { 2 3 //具體業務 4 public void doSomething() { 5 //... 6 super.notifyObserver(); 7 } 8 9 }
4. 具體觀察者
實現Observer介面。
1 public class ConcreteObserver implements Observer { 2 3 @Override 4 public void update() { 5 System.out.println("收到訊息,進行處理"); 6 } 7 8 }
5. Client客戶端
首先建立一個被觀察者,然後定義一個觀察者,將該被觀察者新增到該觀察者的觀察者陣列中,進行測試。
1 public class Client { 2 3 public static void main(String[] args) { 4 //建立一個主題 5 ConcreteSubject subject = new ConcreteSubject(); 6 //定義一個觀察者 7 Observer observer = new ConcreteObserver(); 8 //觀察 9 subject.addObserver(observer); 10 //開始活動 11 subject.doSomething(); 12 } 13 14 }
執行結果如下:
二、觀察者模式的應用
1. 何時使用
- 一個物件狀態改變,所有的依賴物件都將得到通知
2. 方法
- 使用物件導向技術
3. 優點
- 觀察者和被觀察者是抽象耦合的
- 建立了一套觸發機制
4. 缺點
- 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間
- 如果觀察者和觀察目標間有迴圈依賴,可能導致系統崩潰
- 沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的
5. 使用場景
- 關聯行為場景
- 事件多級觸發場景
- 跨系統的訊息變換場景,如訊息佇列的處理機制
6. 應用例項
- 手機丟了,委託別人給其他人發訊息通知
- 通知老師/老闆來了
- 拍賣,拍賣師觀察最高標價,然後通知給其它競價者競價
- 在一個目錄下建立一個檔案,會同時通知目錄管理器增加目錄,並通知磁碟減少空間,檔案是被觀察者,目錄管理器和磁碟管理器是觀察者
- 貓叫了一聲,嚇著了老鼠,也驚到了主人,貓是被觀察者,老鼠和人是觀察者
7. 注意事項
- 避免迴圈引用
- 如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式
三、觀察者模式的實現
下面舉一個具體例項,假設上班時間有一部分同事在看股票,一部分同事在看NBA,這時老闆回來了,前臺通知了部分同事老闆回來了,這些同事及時關閉了網頁沒被發現,而沒被通知到的同事被抓了個現行,被老闆親自“通知”關閉網頁,UML圖如下:
1. 通知者介面
1 public interface Subject { 2 3 //增加 4 public void attach(Observer observer); 5 //刪除 6 public void detach(Observer observer); 7 //通知 8 public void notifyObservers(); 9 10 //狀態 11 public void setAction(String action); 12 public String getAction(); 13 14 }
2. 觀察者
1 public abstract class Observer { 2 3 protected String name; 4 protected Subject subject; 5 6 public Observer(String name, Subject subject) { 7 this.name = name; 8 this.subject = subject; 9 } 10 11 public abstract void update(); 12 13 }
3. 具體通知者
前臺Secretary和老闆Boss作為具體通知者,實現Subject介面。這裡只給出Secretary類的程式碼,Boss類與之類似。
1 public class Secretary implements Subject { 2 3 //同事列表 4 private List<Observer> observers = new LinkedList<>(); 5 private String action; 6 7 //新增 8 @Override 9 public void attach(Observer observer) { 10 observers.add(observer); 11 } 12 13 //刪除 14 @Override 15 public void detach(Observer observer) { 16 observers.remove(observer); 17 } 18 19 //通知 20 @Override 21 public void notifyObservers() { 22 for(Observer observer : observers) { 23 observer.update(); 24 } 25 } 26 27 //前臺狀態 28 @Override 29 public String getAction() { 30 return action; 31 } 32 33 @Override 34 public void setAction(String action) { 35 this.action = action; 36 } 37 38 }
4. 具體觀察者
StockObserver是看股票的同事,NBAObserver是看NBA的同事,作為具體觀察者,繼承Observer類。這裡只給出StockObserver類的程式碼,NBAObserver類與之類似。
1 public class StockObserver extends Observer { 2 3 public StockObserver(String name, Subject subject) { 4 super(name, subject); 5 } 6 7 @Override 8 public void update() { 9 System.out.println(subject.getAction() + "\n" + name + "關閉股票行情,繼續工作"); 10 } 11 12 }
5. 前臺作為通知者進行通知(Client)
前臺作為通知者,通知觀察者。這裡新增adam和tom到通知列表,並從通知列表中刪除了adam,測試沒在通知列表中的物件不會收到通知。
public class Client { public static void main(String[] args) { //前臺為通知者 Secretary secretary = new Secretary(); StockObserver observer = new StockObserver("adam", secretary); NBAObserver observer2 = new NBAObserver("tom", secretary); //前臺通知 secretary.attach(observer); secretary.attach(observer2); //adam沒被前臺通知到,所以被老闆抓了個現行 secretary.detach(observer); //老闆回來了 secretary.setAction("小心!Boss回來了!"); //發通知 secretary.notifyObservers(); } }
執行結果如下,只有tom接收到了通知:
6. 老闆作為通知者進行通知(Client)
老闆作為通知者,通知觀察者。這裡將tom從老闆的通知列表中移除,老闆只通知到了adam。
1 public class Client { 2 3 public static void main(String[] args) { 4 //老闆為通知者 5 Boss boss = new Boss(); 6 7 StockObserver observer = new StockObserver("adam", boss); 8 NBAObserver observer2 = new NBAObserver("tom", boss); 9 10 //老闆通知 11 boss.attach(observer); 12 boss.attach(observer2); 13 14 //tom沒被老闆通知到,所以不用捱罵 15 boss.detach(observer2); 16 17 //老闆回來了 18 boss.setAction("咳咳,我大Boss回來了!"); 19 //發通知 20 boss.notifyObservers(); 21 } 22 23 }
執行結果如下,只有adam捱罵了:
當一個物件的改變需要同時改變其它物件,並且它不知道具體有多少物件有待改變的時候,應該考慮使用觀察者模式。
而使用觀察者模式的動機在於:將一個系統分割成一系列相互協作的類有一個很不好的副作用,就是需要維護相關物件間的一致性,我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴充套件和重用都帶來不便,而觀察者模式所做的工作就是在解除耦合。
原始碼地址:https://gitee.com/adamjiangwh/GoF