观察者模式

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

介绍

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

角色

Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通

知方法notify()。目标类可以是接口,也可以是抽象类或具体类。

ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法

(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。

Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。

ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的

update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。

示例

这里以师生关系为例,老师和学生是一对多的关系,老师给学生布置作业,这个动作作为主题事件,每当老师布置一道题时,就要自动通知到所有的学生把该题记下来,然后再布置下一道题...

Subject接口:

 1 package com.xiaojiesir.observer;
 2 //主题接口
 3  interface Subject {
 4      //添加观察者
 5      void addObserver(Observer obj);
 6      //移除观察者
 7      void deleteObserver(Observer obj);
 8      //当主题方法改变时,这个方法被调用,通知所有的观察者
 9      void notifyObserver();
10 }

Subject接口实现类TeacherSubject:

 1 package com.xiaojiesir.observer;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class TeacherSubject implements Subject {
 7     //用来存放和记录观察者
 8     private List<Observer> observers=new ArrayList<Observer>();
 9     //记录状态的字符串
10     private String info;
11     
12     @Override
13     public void addObserver(Observer obj) {
14         observers.add(obj);
15     }
16 
17     @Override
18     public void deleteObserver(Observer obj) {
19         int i = observers.indexOf(obj);
20         if(i>=0){
21             observers.remove(obj);
22         }
23     }
24 
25     @Override
26     public void notifyObserver() {
27         for(int i=0;i<observers.size();i++){
28             Observer o=(Observer)observers.get(i);
29             o.update(info);
30         }
31     }
32     //布置作业的方法,在方法最后,需要调用notifyObserver()方法,通知所有观察者更新状态
33     public void setHomework(String info){
34         this.info=info;
35         System.out.println("今天的作业是"+info);
36         this.notifyObserver();
37     }
38 
39 }

Oserver接口:

1 package com.xiaojiesir.observer;
2 
3 interface Observer {
4     //当主题状态改变时,会将一个String类型字符传入该方法的参数,每个观察者都需要实现该方法
5     public void update(String info);
6 }

Subject接口实现类TeacherSubject:

 1 package com.xiaojiesir.observer;
 2 
 3 public class StudentObserver implements Observer {
 4 
 5     //保存一个Subject的引用,以后如果可以想取消订阅,有了这个引用会比较方便
 6     private TeacherSubject t;
 7     //学生的姓名,用来标识不同的学生对象
 8     private String name;
 9     //构造器用来注册观察者
10     public Student(String name,Teacher t) {
11         this.name=name;
12         this.t = t;
13         //每新建一个学生对象,默认添加到观察者的行列
14         t.addObserver(this);
15     }
16 
17 
18     @Override
19     public void update(String info) {
20         System.out.println(name+"得到作业:"+info);
21         
22     }
23 
24 }

测试类TestObserver:

 1 package com.xiaojiesir.observer;
 2 
 3 public class TestObserver {
 4 
 5     public static void main(String[] args) {
 6         
 7         TeacherSubject teacher=new TeacherSubject();
 8         StudentObserver zhangSan=new StudentObserver("张三", teacher);
 9         StudentObserver LiSi=new StudentObserver("李四", teacher);
10         StudentObserver WangWu=new StudentObserver("王五", teacher);
11         
12         teacher.setHomework("第二页第六题");
13         teacher.setHomework("第三页第七题");
14         teacher.setHomework("第五页第八题");
15     }
16 }
 1 今天的作业是第二页第六题
 2 张三得到作业:第二页第六题
 3 李四得到作业:第二页第六题
 4 王五得到作业:第二页第六题
 5 今天的作业是第三页第七题
 6 张三得到作业:第三页第七题
 7 李四得到作业:第三页第七题
 8 王五得到作业:第三页第七题
 9 今天的作业是第五页第八题
10 张三得到作业:第五页第八题
11 李四得到作业:第五页第八题
12 王五得到作业:第五页第八题

观察者模式的两种模型

1、推模型

主题对象向观察者推送主题的详细信息,不管观察者是否需要。推送的信息通常是主题对象的全部或部分数据,上面的例子就是典型的推模型

2、拉模型

主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中去获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样观察者在需要获取数据的时候,就可以通过这个引用来获取了。

 

两种模型的比较

1、推模型是假设主题对象知道观察者需要的数据,拉模型是假设主题对象不知道观察者需要什么数据,干脆把自身传递过去,让观察者自己按需要取值

2、推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾到没有考虑到的使用情况,这意味着出现新的情况时,可能要提供新的update()方法

观察者模式总结

观察者模式的主要优点如下:

  • 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
  • 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  • 观察者模式满足 “开闭原则” 的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

观察者模式的主要缺点如下:

  • 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

观察者模式在Java中的应用及解读

JDK 提供的观察者接口

观察者模式在Java语言中的地位非常重要。在JDK的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了JDK对观察者模式的支持。

其中的 Observer 接口为观察者,只有一个 update 方法,当观察目标发生变化时被调用,其代码如下:

1 public interface Observer {
2     void update(Observable o, Object arg);
3 }

Observable 类则为目标类,相比我们的示例中的 TeacherSubject 类多了并发和NPE方面的考虑

 1 public class Observable {
 2     private boolean changed = false;
 3     private Vector<Observer> obs = new Vector();
 4 
 5     public Observable() {
 6     }
 7     // 用于注册新的观察者对象到向量中
 8     public synchronized void addObserver(Observer var1) {
 9         if (var1 == null) {
10             throw new NullPointerException();
11         } else {
12             if (!this.obs.contains(var1)) {
13                 this.obs.addElement(var1);
14             }
15 
16         }
17     }
18     // 用于删除向量中的某一个观察者对象
19     public synchronized void deleteObserver(Observer var1) {
20         this.obs.removeElement(var1);
21     }
22 
23     public void notifyObservers() {
24         this.notifyObservers((Object)null);
25     }
26     // 通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法
27     public void notifyObservers(Object var1) {
28         Object[] var2;
29         synchronized(this) {
30             if (!this.changed) {
31                 return;
32             }
33 
34             var2 = this.obs.toArray();
35             this.clearChanged();
36         }
37 
38         for(int var3 = var2.length - 1; var3 >= 0; --var3) {
39             ((Observer)var2[var3]).update(this, var1);
40         }
41 
42     }
43     // 用于清空向量,即删除向量中所有观察者对象
44     public synchronized void deleteObservers() {
45         this.obs.removeAllElements();
46     }
47     // 该方法被调用后会设置一个boolean类型的内部标记变量changed的值为true,表示观察目标对象的状态发生了变化
48     protected synchronized void setChanged() {
49         this.changed = true;
50     }
51     // 用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的update()方法
52     protected synchronized void clearChanged() {
53         this.changed = false;
54     }
55     // 返回对象状态是否改变
56     public synchronized boolean hasChanged() {
57         return this.changed;
58     }
59     // 返回向量中观察者的数量
60     public synchronized int countObservers() {
61         return this.obs.size();
62     }
63 }

这是一个线程安全的类,是基于Vector实现的。主题对象中有这些方法对观察者进行操作:

利用JDK支持的主题/观察者的例子

创建一个主题:

 1 public class Watched extends Observable
 2 {
 3     private String data = "";
 4     
 5     public String getData()
 6     {
 7         return data;
 8     }
 9     
10     public void setData(String data)
11     {
12         if (!this.data.equals(data))
13         {
14             this.data = data;
15             setChanged();
16         }
17         notifyObservers();
18     }
19 }

创建一个观察者:

 1 public class Watcher implements Observer
 2 {
 3     String data;
 4 
 5     public Watcher(Observable o)
 6     {
 7         o.addObserver(this);
 8     }
 9     
10     public String getData()
11     {
12         return data;
13     }
14     
15     public void update(Observable o, Object arg)
16     {
17         this.data = ((Watched)o).getData();
18         System.out.println("状态发生改变:" + ((Watched)o).getData());
19     }
20 }

测试

 1 public static void main(String[] args)
 2 {
 3     /** 创建被观察者对象 */
 4     Watched watched = new Watched();
 5     
 6     /** 创建观察者对象,并将被观察者对象登记 */
 7     Watcher watcher = new Watcher(watched);
 8     
 9     /** 给被观察者状态赋值 */
10     watched.setData("start");
11     watched.setData("run");
12     watched.setData("stop");
13 }

结果

1 状态发生改变:start
2 状态发生改变:run
3 状态发生改变:stop

看到主题对象改变的时候,观察者对象的状态也随之改变

Spring ApplicationContext 事件机制中的观察者模式

spring的事件机制是从java的事件机制拓展而来,ApplicationContext 中事件处理是由 ApplicationEvent 类和 ApplicationListener 接口来提供的。如果一个Bean实现了 ApplicationListener 接口,并且已经发布

到容器中去,每次 ApplicationContext 发布一个 ApplicationEvent 事件,这个Bean就会接到通知

  • ApplicationContext:事件源,其中的 publishEvent()方法用于触发容器事件
  • ApplicationEvent:事件本身,自定义事件需要继承该类,可以用来传递数据
  • ApplicationListener:事件监听器接口,事件的业务逻辑封装在监听器里面

使用 spring 事件机制重新实现示例

 1 package com.observer.sprintevent;
 2 
 3 import org.springframework.beans.BeansException;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.ApplicationContextAware;
 6 
 7 public class TeacherSubject implements ApplicationContextAware {
 8     private ApplicationContext ctx;
 9     private String name;
10 
11     public TeacherSubject(String name) {
12         this.name = name;
13     }
14 
15     public void setName(String name) {
16         this.name = name;
17     }
18 
19     @Override
20     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
21         this.ctx = applicationContext;
22     }
23 
24     public void setHomework(String homework) {
25         System.out.println(String.format("\n<%s>老师布置的作业是<%s>", this.name,  homework));
26         ctx.publishEvent(new HomeWork(this.name, this.name, homework));
27     }
28 }

 

 1 package com.observer.sprintevent;
 2 
 3 import org.springframework.context.ApplicationEvent;
 4 import org.springframework.context.ApplicationListener;
 5 
 6 public class StudentObserver implements ApplicationListener {
 7     private String username;
 8 
 9     public StudentObserver(String username) {
10         this.username = username;
11     }
12 
13     @Override
14     public void onApplicationEvent(ApplicationEvent event) {
15         if (event instanceof HomeWork) {
16             HomeWork homework = (HomeWork) event;
17             System.out.println(String.format("学生<%s>得到<%s>老师布置的作业是 <%s>", username, homework.getTeacher(), homework.getHomework()));
18         }
19     }
20     public void setUsername(String username) {
21         this.username = username;
22     }
23 }

 

 1 package com.observer.sprintevent;
 2 
 3 import org.springframework.context.ApplicationEvent;
 4 
 5 public class HomeWork extends ApplicationEvent {
 6     /**
 7      * 
 8      */
 9     private static final long serialVersionUID = 2489928953105095902L;
10     private String teacher;
11     private String homework;
12 
13     public HomeWork(Object source, String teacher, String homework) {
14         super(source);
15         this.teacher = teacher;
16         this.homework = homework;
17     }
18 
19     public String getTeacher() {
20         return teacher;
21     }
22 
23     public void setTeacher(String teacher) {
24         this.teacher = teacher;
25     }
26 
27     public String getHomework() {
28         return homework;
29     }
30 
31     public void setHomework(String homework) {
32         this.homework = homework;
33     }
34 
35     
36     
37 }

创建 spring.xml 文件,填入下面的内容

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="TeacherSubject" class="com.observer.sprintevent.TeacherSubject" scope="prototype">
        <constructor-arg name="name" value=""></constructor-arg>
    </bean>
    <bean id="StudentObserver1" class="com.observer.sprintevent.StudentObserver">
        <constructor-arg name="username" value="张三"></constructor-arg>
    </bean>
    <bean id="StudentObserver2" class="com.observer.sprintevent.StudentObserver">
        <constructor-arg name="username" value="李四"></constructor-arg>
    </bean>
    <bean id="StudentObserver3" class="com.observer.sprintevent.StudentObserver">
        <constructor-arg name="username" value="王五"></constructor-arg>
    </bean>
</beans>

测试

 1 package com.observer.sprintevent;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 
 6 public class Test {
 7 
 8     public static void main(String[] args) {
 9         ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
10         
11         TeacherSubject teacher = (TeacherSubject) context.getBean("TeacherSubject");
12         
13         teacher.setName("小杰sir");
14         
15         teacher.setApplicationContext(context);
16         
17         teacher.setHomework("第二页第六题");
18     }
19 }

结果

<小杰sir>老师布置的作业是<第二页第六题>
学生<张三>得到<小杰sir>老师布置的作业是 <第二页第六题>
学生<李四>得到<小杰sir>老师布置的作业是 <第二页第六题>
学生<王五>得到<小杰sir>老师布置的作业是 <第二页第六题>

在此示例中 ApplicationContext 对象的实际类型为 ClassPathXmlApplicationContext,其中的与 publishEvent 方法相关的主要代码如下:

 1 private ApplicationEventMulticaster applicationEventMulticaster;
 2 
 3 public void publishEvent(ApplicationEvent event) {
 4     this.getApplicationEventMulticaster().multicastEvent(event);
 5     if (this.parent != null) {
 6         this.parent.publishEvent(event);
 7     }
 8 }
 9 
10 ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
11     return this.applicationEventMulticaster;
12 }
13 
14 protected void initApplicationEventMulticaster() {
15         ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
16         if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
17             this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
18         } else {
19             this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
20             beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
21         }
22 
23     }

其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通过遍历 ApplicationListener(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor 来并发执行

ApplicationListener 的 onApplicationEvent 方法,与示例本质上是一致的

 1 public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
 2     private Executor taskExecutor;
 3 
 4     public void multicastEvent(final ApplicationEvent event) {
 5         Iterator var2 = this.getApplicationListeners(event).iterator();
 6 
 7         while(var2.hasNext()) {
 8             final ApplicationListener listener = (ApplicationListener)var2.next();
 9             Executor executor = this.getTaskExecutor();
10             if (executor != null) {
11                 executor.execute(new Runnable() {
12                     public void run() {
13                         listener.onApplicationEvent(event);
14                     }
15                 });
16             } else {
17                 listener.onApplicationEvent(event);
18             }
19         }
20 
21     }
22 }

 

转载于:https://www.cnblogs.com/xiaojiesir/p/11118874.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值