设计模式之观察者模式学习理解


当对象间存在一对多关系时,则使用观察者模式(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-2020 CODEPRJ.COM