目录
7.3 使用Observable 与 Observer 改造案例
一、观察者模式是什么
【定义】:多个对象间存在一对多的依赖关系,当一个对象发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它属于行为型模式。
【主要作用】:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
【结构】:与发布-订阅模式结构类似
二、观察者模式的适用场景
-
当一个对象发生改变时,需要其他对象随之变化,但是又不想这些对象有比较高的耦合。
-
当一个对象发生改变时,需要其他对象随之变化,但是又不知道具体有哪些对象需要被通知变化。
-
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
【生活场景】
-
交叉路口的红灯停,绿灯行
-
学校的铃声响起来,学生老师上课下课
-
微信公众号推送消息给微信用户
-
物价上涨的商家和消费者
三、观察者模式结构
-
抽象主题(Subject)角色:抽象目标类,提供了一个用于保存观察者对象的引用和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
-
具体主题(Concrete Subject)角色:具体目标类,实现抽象目标中的通知方法。当具体主题的发生改变时,通知所有注册过的观察者对象。
-
抽象观察者(Observer)角色:抽象观察者类, 一般情况下该接口只包含一个
update
抽象方法。 该方法在接到具体主题的更改通知时被调用。 -
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
当具体主题有多个时,可根据业务需要将List放在具体主题角色中进行add/ remove ,这样可以实现不同的具体主题通知不同的观察者
四、观察者模式实现方式
-
首先将业务逻辑划分为一个发布者和一组订阅者
-
声明抽象主题(Subject)接口(发布者),定义在列表中添加和删除订阅对象的行为方法。以及通知所有观察者的抽象方法。
-
声明具体主题(Concrete Subject)类,实现抽象主题。
-
一般而言对于添加和删除订阅对象的方法实现都一样, 因此将列表中添加和删除方法放置在直接扩展自发布者接口的抽象类中是可以避免代码冗余。 具体发布者扩展该类从而继承所有的订阅行为。
-
但是需要在现有的类层次结构中应用该模式。可以使用组合模式: 将列表中添加和删除的订阅逻辑放入一个独立的对象,让所有实际订阅者使用该对象。
-
-
声明抽象观察者(Observer)接口(订阅者),该接口至少应声明一个
update
方法。 -
声明具体观察者(Concrete Observer),实现通知更新的方法。
-
客户端负责生成所需的全部订阅者, 并在相应的发布者处完成注册工作。
五、观察者模式的实现
【案例】:交叉路口的红灯停,绿灯行。
【案例说明】:在红绿灯的案例模型中,红绿灯属于发布者,而路上的行人和汽车是一组订阅者。
-
在发布者中,实现三个方法:添加和删除路上的行人和汽车这个两个订阅对象的行为方法,以及当红绿灯变化的时候通知订阅对象的行为方法。
-
在订阅者中,实现一个方法:当收到发布者发布的红绿灯变化通知时,作出不同的行为。
-
抽象主题(Subject)接口(发布者)
import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 抽象主题(Subject)接口(发布者) * 由于添加和删除订阅对象的方法实现都一样,所以这里使用抽象类, * 这样在具体主题中就不用重复实现了 * 也可以使用接口,在不同的具体主题实现不同的添加和删除订阅对象 */ publicabstractclass TrafficLightsSubject { /** * 缓存具体的观察者, * 当观察者有多组时,可以下沉到不同的具体主题中 */ private List<Observer> observers = new ArrayList<>(); /** * 添加具体的观察者, * 当观察者有多组时,可以下沉到不同的具体主题中实现 */ public void add(Observer observer){ observers.add(observer); } /** * 删除具体的观察者, * 当观察者有多组时,可以下沉到不同的具体主题中实现 */ public void remove(Observer observer){ observers.remove(observer); } /** * 当事件发生时,通知绑定在该事件源上的所有订阅者(调用事件处理方法) * @param type */ public void notifies(String type) { Iterator<Observer> iterator = observers.iterator(); while (iterator.hasNext()) { Observer ren = iterator.next(); ren.update(type); } } }
-
具体主题(Concrete Subject)
注意:
/** * 具体主题(Concrete Subject) * 由于在此案例中添加删除的方法在父类中已经实现,这里就直接调用即可 */ publicclass TrafficLightsConcreteSubject extends TrafficLightsSubject { @Override public void add(Observer observer) { super.add(observer); } @Override public void remove(Observer observer) { super.remove(observer); } @Override public void notifies(String type) { super.notifies(type); } }
-
此案例,发布者只有红绿灯一种,抽象主题可以不用,直接使用具体主题即可
- 假设发布者还有在坏了需要通知维修工程师维修的行为,那此时就必须实现两种不一样的具体主题通知
-
-
抽象观察者(Observer)接口(订阅者)
/** * 抽象观察者(Observer)(订阅者) */ publicinterface Observer { /** * 接收发布的红绿灯变化通知时,作出不同的行为 * @param type 红绿灯 */ public void update(String type); }
-
具体观察者(Concrete Observer)
/** * 具体观察者(Concrete Observer) 汽车 */ publicclass CarObserver implements Observer { @Override public void update(String type) { if ("red".equalsIgnoreCase(type)) { System.out.println("红灯...横穿马路的汽车停止过马路"); } else { System.out.println("绿灯...汽车开始横穿马路"); } } } /** * 具体观察者(Concrete Observer) 行人 */ publicclass PeopleObserver implements Observer { @Override public void update(String type) { if ("red".equalsIgnoreCase(type)) { System.out.println("红灯...横穿马路的行人停止过马路"); } else { System.out.println("绿灯...行人开始横穿马路"); } } }
-
客户端代码实现
public static void main(String[] args) throws InterruptedException { String colour = "red"; /** * 客户端负责生成所需的全部订阅者, 并在相应的发布者处完成注册工作。 */ TrafficLightsSubject subject = new TrafficLightsConcreteSubject(); subject.add(new PeopleObserver()); subject.add(new CarObserver()); /** * 这里只是一个示例,只是简单的考虑了单方向的情况,即:相同方向的汽车和行人 */ while (true){ Thread.sleep(300); System.out.println("此时是对面的红绿灯是:" + (colour.equals("red")? "红":"绿") + "灯," + "相同方向的汽车和行人的行为如下:"); subject.notifies(colour); colour = colour.equals("red")? "green":"red"; } }
-
案例输出结果
六、观察者模式的优缺点
-
优点
-
符合依赖倒置原则,降低了发布者与观察者之间的耦合。
-
发布者与观察者之间建立了一套触发机制
-
符合开-闭原则,主题接口仅仅依赖于观察者接口
-
-
缺点
-
主题(发布者)与观察者之间可能出现循环引用。
-
当观察者对象很多时,通知的发布耗时,影响程序的效率。
-
七、观察者模式在Java中的应用
Java中定义了两个接口实现了观察者模式,我们只需要实现这两个接口即可
-
java.util.Observable
-
java.util.Observer
7.1 Observable 抽象主题类
拥有有一个 Vector向量,用于保存所有要通知的观察者对象,拥有4 个重要方法 :
-
void addObserver(Observer o)
用于将新的观察者对象添加到向量中。
public synchronized void addObserver(Observer o) { if (o == null) thrownew NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } }
-
void deleteObserver(Observer o)
用于将向量中观察者对象移除。
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
-
void notifyObservers(Object arg)
调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常后加入向量的观察者越先得到通知。
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
-
void setChange()
用来设置一个 boolean 类型的内部标志位changed,注明目标对象发生了变化。当changed = true时,notifyObservers() 才会通知观察者。默认changed = false
protected synchronized void setChanged() {
changed = true;
}
7.2 Observer 抽象观察者
监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
package java.util;
publicinterface Observer {
void update(Observable o, Object arg);
}
7.3 使用Observable 与 Observer 改造案例
-
抽象主题(Subject)接口(发布者)
抽象主题不需要自己定义,具体主题直接使用java.util.Observable
-
具体主题(Concrete Subject)
import java.util.Observable; import java.util.Observer; /** * 具体主题(Concrete Subject) * 由于在此案例中添加删除的方法在父类中已经实现,这里就直接调用即可 */ publicclass TrafficLightsConcreteSubject extends Observable { @Override public synchronized void addObserver(Observer o) { super.addObserver(o); } /** * 当事件发生时,通知绑定在该事件源上的所有订阅者(调用事件处理方法) * @param arg */ @Override public void notifyObservers(Object arg) { /** * 设置内部标志位,注明目标对象发生了变化 */ super.setChanged(); /** * 通知观察者发布者状态发生变化 */ super.notifyObservers(arg); } }
-
抽象观察者(Observer)接口(订阅者)
抽象主题不需要自己定义,具体主题直接使用java.util.Observer
-
具体观察者(Concrete Observer)
import java.util.Observable; import java.util.Observer; /** * 具体观察者(Concrete Observer) 行人 */ publicclass CarObserver implements Observer { @Override public void update(Observable o, Object arg) { if ("red".equalsIgnoreCase((String)arg)) { System.out.println("红灯...横穿马路的汽车停止过马路"); } else { System.out.println("绿灯...汽车开始横穿马路"); } } } /** * 具体观察者(Concrete Observer) 行人 */ publicclass CarObserver implements Observer { @Override public void update(Observable o, Object arg) { if ("red".equalsIgnoreCase((String)arg)) { System.out.println("红灯...横穿马路的汽车停止过马路"); } else { System.out.println("绿灯...汽车开始横穿马路"); } } }
-
客户端代码实现
public static void main(String[] args) throws InterruptedException { String colour = "red"; /** * 客户端负责生成所需的全部订阅者, 并在相应的发布者处完成注册工作。 */ Observable subject = new TrafficLightsConcreteSubject(); subject.addObserver(new PeopleObserver()); subject.addObserver(new CarObserver()); /** * 这里只是一个示例,只是简单的考虑了单方向的情况,即:相同方向的汽车和行人 */ while (true){ Thread.sleep(300); System.out.println("此时是对面的红绿灯是:" + (colour.equals("red")? "红":"绿") + "灯," + "相同方向的汽车和行人的行为如下:"); subject.notifyObservers(colour); colour = colour.equals("red")? "green":"red"; } }
八、观察者模式和其他模式的区别
观察者模式与其他类似模式在处理请求发送者和接收者之间的连接方式不同:
-
【责任链模式】按照顺序将请求动态传递给一系列的接收者, 直至其中一名接收者对请求进行处理。
-
【命令模式】在发送者和请求者之间建立单向连接。
-
【中介者模式】清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
-
【观察者模式】允许接收者动态地订阅或取消接收请求。
九、总结
实现观察者模式的时,发布者和观察者之间不能直接调用,否则会使发布者和观察者之间紧密的耦合,从根本上违反面向对象的设计的原则。