設計模式之觀察者模式學習理解


當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行為型模式

介紹

意圖:定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。

主要解決:一個對象狀態改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。

何時使用:一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。

如何解決:使用面向對象技術,可以將這種依賴關系弱化。

關鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。

應用實例: 1、京東上某個商品暫時沒貨,提示用戶關注后到貨通知,這個暫時無貨的商品是被觀察者,點擊關注這個商品的用戶就是觀察者。 2、老師針對成績在60分以下的同學定期發送最新的考題分析郵件,每輪考試下來都會有不及格的同學,由不及格變為及格的同學自動從郵件列表里移除,新的不及格的同學會被加進郵件列表里。

優點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。

缺點: 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。

使用場景: 1、有多個子類共有的方法,且邏輯相同。 2、重要的、復雜的方法,可以考慮作為模板方法。

注意事項: 1、JAVA 中已經有了對觀察者模式的支持類。 2、避免循環引用。 3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般采用異步方式。

下面這個實例是系統中常會遇到的:

場景描述:
* 系統有一個模塊:工單管理,它有三個子模塊,工單創建,工單激活,工單處理
* 系統有兩個角色:管理員、安裝工
* 管理員可以操作所有的子模塊,安裝工只能操作工單處理
* 現在需要添加刪除子模塊的時候自動通知到對應的角色,使其添加刪除對應的模塊權限

 

自己實現的觀察者模式

被觀察者類

 1 /**
 2  * 工單管理  被觀察者類
 3  * @author ko
 4  *
 5  */
 6 public class OrderManage {
 7 
 8     private List<AbstractRole> roles = new ArrayList<>();// 觀察者集合    角色
 9     private List<String> submodules = new ArrayList<>();// 子模塊集合
10     
11     public void attach(AbstractRole role){
12         roles.add(role);
13     }
14     
15     // 刪除某個子模塊
16     public void removeSubmodule(String submodule){
17         submodules.remove(submodule);
18         // 刪除好后通知觀察者
19         notifyRelevantRoles("removeSubmodule "+submodule);
20     }
21     
22     // 添加某個子模塊
23     public void addSubmodule(String submodule){
24         if (!submodules.contains(submodule)) {
25             submodules.add(submodule);
26             // 添加好后通知觀察者
27             notifyRelevantRoles("addSubmodule "+submodule);
28         }
29     }
30 
31     private void notifyRelevantRoles(String msg) {
32         for (AbstractRole role : roles) {
33             role.update(msg);
34         }
35     }
36     
37 }

抽象觀察者類

 1 /**
 2  * 抽象觀察者類 抽象角色類
 3  * @author ko
 4  *
 5  */
 6 public abstract class AbstractRole {
 7     
 8     protected OrderManage orderManage;
 9     protected String roleName;
10     
11     public abstract void update(String msg);
12 
13 }

具體觀察者類,管理員

 1 /**
 2  * 具體觀察者類,管理員角色不管操作的是什么子模塊,都更新相關信息
 3  * @author ko
 4  *
 5  */
 6 public class Administrators extends AbstractRole {
 7     
 8     public Administrators(OrderManage orderManage,String roleName) {
 9         this.roleName = roleName;
10         this.orderManage = orderManage;
11         orderManage.attach(this);// 注冊觀察者到被觀察者
12     }
13 
14     @Override
15     public void update(String msg) {
16         System.out.println(roleName+"receive OrderManage msg:"+msg);
17     }
18 
19 }

具體觀察者類,安裝工

 1 /**
 2  * 具體觀察者類,安裝工角色只能操作工單處理子模塊,更新相關信息
 3  * @author ko
 4  *
 5  */
 6 public class Installer extends AbstractRole {
 7 
 8     public Installer(OrderManage orderManage,String roleName) {
 9         this.roleName = roleName;
10         this.orderManage = orderManage;
11         orderManage.attach(this);// 注冊觀察者到被觀察者
12     }
13 
14     @Override
15     public void update(String msg) {
16         if (msg.contains("工單處理")) {
17             System.out.println(roleName+"receive OrderManage msg:"+msg);
18         }
19     }
20 
21 }

測試類

 1 /**
 2  * 測試類
 3  * 觀察者模式 Observer Pattern
 4  * 場景描述:
 5  *     系統有一個模塊:工單管理,它有三個子模塊,工單創建,工單激活,工單處理
 6  *  系統有兩個角色:管理員、安裝工
 7  *  管理員可以操作所有的子模塊,安裝工只能操作工單處理
 8  *  現在需要添加刪除子模塊的時候自動通知到對應的角色,使其添加刪除對應的模塊權限
 9  * @author ko
10  *
11  */
12 public class Test {
13 
14     public static void main(String[] args) {
15         OrderManage orderManage = new OrderManage();
16         new Administrators(orderManage,"管理員");
17         new Installer(orderManage,"安裝工");
18         
19         
20         // 添加一個子模塊 工單創建
21         System.out.println("創建子模塊工單創建");
22         orderManage.addSubmodule("工單創建");
23         
24         System.out.println("");
25         System.out.println("創建子模塊工單處理");
26         orderManage.addSubmodule("工單處理");
27         
28         System.out.println("");
29         System.out.println("創建子模塊工單激活");
30         orderManage.addSubmodule("工單激活");
31         
32         System.out.println("");
33         System.out.println("刪除子模塊工單處理");
34         orderManage.removeSubmodule("工單處理");
35     }
36     
37 }

運行結果

創建子模塊工單創建
管理員receive OrderManage msg:addSubmodule 工單創建

創建子模塊工單處理
管理員receive OrderManage msg:addSubmodule 工單處理
安裝工receive OrderManage msg:addSubmodule 工單處理

創建子模塊工單激活
管理員receive OrderManage msg:addSubmodule 工單激活

刪除子模塊工單處理
管理員receive OrderManage msg:removeSubmodule 工單處理
安裝工receive OrderManage msg:removeSubmodule 工單處理

  

java自帶的觀察者模式

Observer對象是觀察者,Observable對象是被觀察者。

a.實現觀察者模式
實現觀察者模式非常簡單,
[1]創建被觀察者類,它繼承自java.util.Observable類;
[2]創建觀察者類,它實現java.util.Observer接口;
b.對於被觀察者類
添加它的觀察者:
void addObserver(Observer o)
addObserver()方法把觀察者對象添加到觀察者對象列表中

當被觀察者中的事件發生變化時,執行
setChanged();
notifyObservers();
setChange()方法用來設置一個內部標志位注明數據發生了變化;notifyObservers()方法會去調用觀察者對象列表中所有的Observer的update()方法,通知它們數據發生了變化。
只有在setChange()被調用后,notifyObservers()才會去調用update()。
c.對於觀察者類,實現Observer接口的唯一方法update

void update(Observable o, Object arg)

形參Object arg,對應一個由notifyObservers(Object arg);傳遞來的參數,當執行的是notifyObservers();時,arg為null。

 

使用java自帶的封裝的類實現觀察者模式
* 場景模擬:報警管理
* 汽車租賃公司把車子租出去后,還是要監控車子的使用情況,防止不法之徒動歪心思,給公司造成損失。
* 當出現以下情形時,汽車會發出報警,相關人員接到報警信息后,會及時跟進處理。
* A:進出圍欄報警
* B:gps終端拆除報警
* C:二押報警
* 追車員可以接收到B C報警,數據分析員可以接收到A B C類報警,銀行抵押代理可以收到C類報警

 

被觀察者類

 1 /**
 2  * 被觀察者類
 3  * 報警管理
 4  * @author ko
 5  *
 6  */
 7 public class AlarmManage extends Observable {
 8     
 9     private String msg = "";
10     
11     public String getMsg() {
12         return msg;
13     }
14 
15     public void setMsg(String msg) {
16         System.out.println(msg);
17         if (!this.msg.equals(msg)) {
18             this.msg = msg;
19             setChanged();// 改變通知者的狀態
20         }
21         notifyObservers();// 調用父類Observable方法,通知所有觀察者
22     }
23   
24     // 具體業務操作 當前環境下就是 模擬報警觸發
25     public void simulationAlarm(String carNum,String alarmType){
26         setMsg(carNum+"觸發了:"+alarmType);
27     }
28     // 下面的這些方法沒有特殊需求可以不重寫,這邊列出來只是為了了解一下jdk源碼
29     // 如果觀察者與集合中已有的觀察者不同,則向對象的觀察者集中添加此觀察者。
30     @Override
31     public synchronized void addObserver(Observer o) {
32         // TODO Auto-generated method stub
33         super.addObserver(o);
34     }
35 
36     // 指示對象不再改變,或者它已對其所有的觀察者通知了最近的改變,所以 hasChanged 方法將返回 false。
37     @Override
38     protected synchronized void clearChanged() {
39         // TODO Auto-generated method stub
40         super.clearChanged();
41     }
42 
43     // 返回 Observable 對象的觀察者數目
44     @Override
45     public synchronized int countObservers() {
46         // TODO Auto-generated method stub
47         return super.countObservers();
48     }
49 
50     // 從對象的觀察者集合中刪除某個觀察者
51     @Override
52     public synchronized void deleteObserver(Observer o) {
53         // TODO Auto-generated method stub
54         super.deleteObserver(o);
55     }
56     
57     // 清除觀察者列表,使此對象不再有任何觀察者。
58     @Override
59     public synchronized void deleteObservers() {
60         // TODO Auto-generated method stub
61         super.deleteObservers();
62     }
63 
64     // 測試對象是否改變。
65     @Override
66     public synchronized boolean hasChanged() {
67         // TODO Auto-generated method stub
68         return super.hasChanged();
69     }
70 
71     // 如果 hasChanged方法指示對象已改變,則通知其所有觀察者,並調用 clearChanged 方法來指示此對象不再改變。
72     @Override
73     public void notifyObservers() {
74         // TODO Auto-generated method stub
75         super.notifyObservers();
76     }
77 
78     // 如果 hasChanged 方法指示對象已改變,則通知其所有觀察者,並調用 clearChanged 方法來指示此對象不再改變。
79     @Override
80     public void notifyObservers(Object arg) {
81         // TODO Auto-generated method stub
82         super.notifyObservers(arg);
83     }
84     
85     // 標記此 Observable 對象為已改變的對象;現在 hasChanged 方法將返回 true。
86     @Override
87     protected synchronized void setChanged() {
88         // TODO Auto-generated method stub
89         super.setChanged();
90     }
91     
92     
93 }

觀察者類

 1 /**
 2  * 觀察者類
 3  * 追車員
 4  * 只能接收到 gps終端拆除報警、二押報警
 5  * @author ko
 6  *
 7  */
 8 public class ChaseCarPerson implements Observer {
 9 
10     private String pname;
11     
12     public ChaseCarPerson(String pname, AlarmManage alarmManage) {
13         this.pname = pname;
14         alarmManage.addObserver(this);
15     }
16 
17     @Override
18     public void update(Observable observable, Object arg1) {
19         String msg = ((AlarmManage) observable).getMsg();
20         if(msg.contains("gps終端拆除報警") || msg.contains("二押報警")){
21             System.out.println("追車員"+pname+"接收到報警信息:"+msg);
22         }
23     }
24 
25 }
 1 /**
 2  * 觀察者類
 3  * 數據分析員
 4  * 能接收到所有報警
 5  * @author ko
 6  *
 7  */
 8 public class AnalysisPerson implements Observer {
 9 
10     private String pname;
11     
12     public AnalysisPerson(String pname, AlarmManage alarmManage) {
13         this.pname = pname;
14         alarmManage.addObserver(this);
15     }
16     
17     @Override
18     public void update(Observable observable, Object arg1) {
19         String msg = ((AlarmManage) observable).getMsg();
20         System.out.println("數據分析員"+pname+"接收到報警信息:"+msg);
21     }
22 
23 }
 1 /**
 2  * 觀察者類
 3  * 銀行代理員
 4  * 只能接收到二押報警
 5  * @author ko
 6  *
 7  */
 8 public class BankAgentPerson implements Observer {
 9 
10     private String pname;
11     
12     public BankAgentPerson(String pname, AlarmManage alarmManage) {
13         this.pname = pname;
14         alarmManage.addObserver(this);
15     }
16     
17     @Override
18     public void update(Observable observable, Object arg1) {
19         String msg = ((AlarmManage) observable).getMsg();
20         if (msg.contains("二押報警")) {
21             System.out.println("銀行代理員"+pname+"接收到報警信息:"+msg);
22         }
23     }
24 
25 }

測試類

 1 /**
 2  * 測試類
 3  * 使用java自帶的封裝的類實現觀察者模式
 4  * 場景模擬:報警管理
 5  *         汽車租賃公司把車子租出去后,還是要監控車子的使用情況,防止不法之徒動歪心思,給公司造成損失。
 6  *         當出現以下情形時,汽車會發出報警,相關人員接到報警信息后,會及時跟進處理。
 7  *         A:進出圍欄報警
 8  *         B:gps終端拆除報警
 9  *         C:二押報警
10  *         追車員可以接收到B C報警,數據分析員可以接收到A B C類報警,銀行抵押代理可以收到C類報警
11  * @author ko
12  *
13  */
14 public class Test {
15     static String[] alarmTypes = new String[]{"進出圍欄報警","gps終端拆除報警","二押報警"};
16     static String[] cars = new String[]{"滬A125KL","蒙A985NL","閔D668YY"};
17     
18     public static void main(String[] args) {
19         // 新建被觀察者
20         AlarmManage alarmManage = new AlarmManage();
21         
22         // 新建觀察者的同時就將其加入到了被觀察者
23         ChaseCarPerson chaseCarPerson = new ChaseCarPerson("mr wang", alarmManage);
24         AnalysisPerson analysisPerson = new AnalysisPerson("mr li", alarmManage);
25         BankAgentPerson bankAgentPerson = new BankAgentPerson("mr sun", alarmManage);
26         
27         System.out.println("被觀察者報警管理里共有"+alarmManage.countObservers()+"個觀察者。");
28         
29         // 觸發報警
30         for (int i = 0; i < alarmTypes.length; i++) {
31             alarmManage.simulationAlarm(getCarNumRandom(), getAlarmTypeRandom());
32             try {
33                 Thread.sleep(3000);
34             } catch (InterruptedException e) {
35                 e.printStackTrace();
36             }
37             System.out.println("");
38         }
39         
40         // 從被觀察者刪除觀察者追車員
41         alarmManage.deleteObserver(chaseCarPerson);
42         System.out.println("從被觀察者刪除追車員");
43         
44         System.out.println("被觀察者報警管理里還有"+alarmManage.countObservers()+"個觀察者。");
45     }
46     
47     public static String getCarNumRandom(){
48         return cars[new Random().nextInt(3)];
49     }
50     public static String getAlarmTypeRandom(){
51         return alarmTypes[new Random().nextInt(3)];
52     }
53 }

運行結果

被觀察者報警管理里共有3個觀察者。

閔D668YY觸發了:gps終端拆除報警
數據分析員mr li接收到報警信息:閔D668YY觸發了:gps終端拆除報警
追車員mr wang接收到報警信息:閔D668YY觸發了:gps終端拆除報警

蒙A985NL觸發了:進出圍欄報警
數據分析員mr li接收到報警信息:蒙A985NL觸發了:進出圍欄報警

滬A125KL觸發了:進出圍欄報警
數據分析員mr li接收到報警信息:滬A125KL觸發了:進出圍欄報警

從被觀察者刪除追車員

被觀察者報警管理里還有2個觀察者。

 

總結

可以看出jdk自帶的觀察者模式還是比自己實現的代碼更健全,考慮到了多線程情況下的使用,加入了同步機制。另外redis里的pub/sub也有這樣的功能,可以實現觀察者模式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM