一. 什么是观察者模式?
一个对象的行为 依赖于 另一个对象的状态。换一种说法,当被观察对象(目标对象)的状态发生改变时 ,会直接影响到观察对象的行为。
这里涉及到几个对象:
- 观察者
- 被观察者
- 状态的变化
举个栗子: 高中上晚自习的时候, 总会有一波调皮的同学, 趁老师不在, 在教室里打扑克, 然后找一个人同学在门口放哨, 老师来了, 赶快通知里面打扑克的同学, 同学们赶紧收起扑克, 进入仔细状态. 我们来分析一下这里的角色
- 观察者: 站在教师放哨的同学
- 被观察者: 老师
- 事件: 当被观察者老师向教师走来的时候, 观察者发出信号给里面玩扑克的同学, 老师来了, 快收起来了.
观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。UML结构图如下:
被观察者 和观察者 一般是 一对多的关系,一个被观察者 对应多个观察者,当一个被观察者的状态发生改变时,被观察者通知观察者, 然后可以在观察者内部 进行业务逻辑的处理。
二、观察者模式的原理
根据上线的UML图, 我们知道被观察者主要有以下几个部分
- 抽象的被观察者Subject:被观察者发生变化, 需要通知观察者。
- 具体被观察者ConcreteSubject:通茶具体的被观察者只有1个。
- 抽象的观察者Observer :接收变化,并执行相应的逻辑。
- 具体的观察者ConcreteObserver:通常具体的观察者有1个或者多个。
- 客户端client
下面来看看代码实现:
- 抽象的被观察者
package com.lxl.www.designPatterns.observerPattern.observer;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* 被观察者
*/
public abstract class Subject {
/**
* 一个被观察者对应多个观察者
*/
List<Observer> observers = new ArrayList<Observer>();
/**
* 增加被观察者
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* 删除被观察者
*/
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
/**
* 通知被观察者
*/
public void notifyObserver() {
for (Observer observer: observers) {
// 通知观察者处理
observer.update();
}
}
abstract void event();
}
- 具体被观察者
package com.lxl.www.designPatterns.observerPattern.observer;
/**
* 具体的观察者
*/
public class ConcreteSubject extends Subject{
/**
* 发生了什么事件
*/
public void event() {
System.out.println("我是【被观察者】----发生了某个事件, 通知观察者们");
super.notifyObserver();
}
}
- 抽象的观察者
package com.lxl.www.designPatterns.observerPattern.observer;
/**
* 观察者
*/
public abstract class Observer {
abstract void update();
}
- 具体的观察者
package com.lxl.www.designPatterns.observerPattern.observer;
public class ConcreteOberverA extends Observer{
@Override
void update() {
System.out.println("我是观察者A---我得到了通知, 执行相关处理逻辑");
}
}
public class ConcreteOberverB extends Observer{
@Override
void update() {
System.out.println("我是观察者B---我得到了通知, 执行相关处理逻辑");
}
}
- 客户端client
package com.lxl.www.designPatterns.observerPattern.observer;
public class Client {
public static void main(String[] args) {
// 有一个被观察者
Subject subject = new ConcreteSubject();
// 有两个观察者
Observer observerA = new ConcreteOberverA();
Observer observerB = new ConcreteOberverB();
subject.addObserver(observerA);
subject.addObserver(observerB);
// 被观察者发出一个事件,通知观察者们
subject.event();
}
}
运行结果:
我是【被观察者】----发生了某个事件, 通知观察者们
我是观察者A---我得到了通知, 执行相关处理逻辑
我是观察者B---我得到了通知, 执行相关处理逻辑
三. 观察者模式案例
我们现在每天都是用微信, 也关注了不少微信公众号. 如果一微信公众号为例, 我们来思考一下, 如果使用观察者模式实现. 现在有一个微信公众号, 有n的微信用户关注了微信公众号, 当微信公众号发布一个消息的时候, 会通知所有关注了公众号的微信用户.
分析:
- 被观察者: 微信公众号. 里面有一个记录了关注我的微信用户的集合.
- 具体被观察者: 我的微信公众号
- 观察者: 微信用户
- 具体观察者: 关注了微信公众号的微信用户A, B, C等
- 微信公众号客户端: 在客户端发送消息
下面来看看源代码:
- 被观察者
package com.lxl.www.designPatterns.observerPattern.weChat;
import java.util.ArrayList;
import java.util.List;
public abstract class WeChatPublicAccount {
List<WeChatObserver> weChatObservers = new ArrayList<WeChatObserver>();
/**
* 添加微信用户
* @param weChatUser
*/
public void addWeChatObserver(WeChatObserver weChatUser) {
System.out.println("添加微信观察者" + weChatUser.getName() + "");
weChatObservers.add(weChatUser);
}
/**
* 删除微信用户
* @param weChatUser
*/
public void delWeChatObserver(WeChatObserver weChatUser) {
System.out.println("删除微信观察者" + weChatUser.getName() + "");
weChatObservers.remove(weChatUser);
}
public void notifyWeChatObserver() {
for(WeChatObserver weChatObserver: weChatObservers) {
System.out.println("向观察者" + weChatObserver.getName() + "发送消息");
weChatObserver.receive();
}
}
public abstract void writeArticle();
public abstract void promotions();
}
- 具体被观察者: 我的微信公众号
package com.lxl.www.designPatterns.observerPattern.weChat;
/**
* 我的微信公众号
*/
public class MyWeChatPublicAccount extends WeChatPublicAccount{
@Override
public void writeArticle() {
System.out.println("写了一篇文章, 通知所有关注了我的微信用户");
super.notifyWeChatObserver();
}
@Override
public void promotions() {
System.out.println("发起一个促销活动, 通知所有关注了我的微信用户");
super.notifyWeChatObserver();
}
}
- 观察者: 微信用户
package com.lxl.www.designPatterns.observerPattern.weChat;
public abstract class WeChatObserver {
protected String name;
/**
* 接收消息通知
* @return
*/
public abstract void receive() ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 具体观察者: 关注了微信公众号的微信用户A, B, C等
package com.lxl.www.designPatterns.observerPattern.weChat;
public class WeChatObserverZhangsan extends WeChatObserver{
public WeChatObserverZhangsan() {
super.name = "zhangSan";
}
/**
* 接收消息通知
* @return
*/
public void receive() {
System.out.println("zhangsan 收到了消息");
}
}
package com.lxl.www.designPatterns.observerPattern.weChat;
public class WeChatObserverLisi extends WeChatObserver{
public WeChatObserverLisi() {
super.name = "lisi";
}
/**
* 接收消息通知
* @return
*/
public void receive() {
System.out.println("lisi 收到了消息");
}
}
- 微信公众号客户端
package com.lxl.www.designPatterns.observerPattern.weChat;
public class Client {
public static void main(String[] args) {
// 被观察者
WeChatPublicAccount publicAccount = new MyWeChatPublicAccount();
// 观察者
WeChatObserver observerZhangsan = new WeChatObserverZhangsan();
WeChatObserver observerLisi = new WeChatObserverLisi();
publicAccount.addWeChatObserver(observerZhangsan);
publicAccount.addWeChatObserver(observerLisi);
System.out.println("");
System.out.println();
// 写了一篇文章, 广播给大家
publicAccount.writeArticle();
System.out.println("");
System.out.println();
// 观察者lisi取消关注
publicAccount.delWeChatObserver(observerLisi);
System.out.println("");
System.out.println();
// 有一个促销活动广播给大家
publicAccount.promotions();
}
}
运行结果:
添加微信观察者zhangSan
添加微信观察者lisi写了一篇文章, 通知所有关注了我的微信用户
向观察者zhangSan发送消息
zhangsan 收到了消息
向观察者lisi发送消息
lisi 收到了消息删除微信观察者lisi
发起一个促销活动, 通知所有关注了我的微信用户
向观察者zhangSan发送消息
zhangsan 收到了消息
我们可以看出, 张三和李四都关注了, 则两人都可以收到消息, 李四取消关注了, 就收不到后面的消息了.
四. 观察者模式扩展
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1. Observable类 Observable 类是抽象目标类,它有一个 Vector 向量(可以将其理解为List),用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。 a) void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。 b) void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。 c) void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。 2. Observer 接口 Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作
我们来改造微信公众号广播微信消息的场景
- 定义一个我们的公众号, 继承自Observable抽象类
package com.lxl.www.designPatterns.observerPattern.jdkObserver;
import com.lxl.www.designPatterns.observerPattern.weChat.WeChatPublicAccount;
import java.util.Observable;
/**
* 被观察者
* 我的微信公众号
*/
public class MyWeChatPublicAccount extends Observable {
public void writeArticle() {
System.out.println("写了一篇文章, 通知所有关注了我的微信用户");
super.setChanged();
super.notifyObservers();
}
public void promotions() {
System.out.println("发起一个促销活动, 通知所有关注了我的微信用户");
super.setChanged();
super.notifyObservers();
}
}
- 定义观察者, 实现Observer接口
package com.lxl.www.designPatterns.observerPattern.jdkObserver;
import com.lxl.www.designPatterns.observerPattern.weChat.WeChatObserver;
import java.util.Observable;
import java.util.Observer;
public class WeChatObserverZhangsan implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("zhangsan 收到发送过来的消息");
}
}
package com.lxl.www.designPatterns.observerPattern.jdkObserver;
import com.lxl.www.designPatterns.observerPattern.weChat.WeChatObserver;
import java.util.Observable;
import java.util.Observer;
public class WeChatObserverLisi implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("lisi 收到发送过来的消息");
}
}
- 定义一个客户端, 模拟放松消息
package com.lxl.www.designPatterns.observerPattern.jdkObserver;
import java.util.Observable;
import java.util.Observer;
public class Client {
public static void main(String[] args) {
// 被观察者
MyWeChatPublicAccount observable = new MyWeChatPublicAccount();
// 观察者
Observer observerZhangsan = new WeChatObserverZhangsan();
Observer observerLisi = new WeChatObserverLisi();
observable.addObserver(observerZhangsan);
observable.addObserver(observerLisi);
System.out.println("");
System.out.println();
// 写了一篇文章, 广播给大家
observable.writeArticle();
System.out.println("");
System.out.println();
// 观察者lisi取消关注
observable.deleteObserver(observerLisi);
System.out.println("");
System.out.println();
// 有一个促销活动广播给大家
observable.promotions();
}
}
运行结果:
写了一篇文章, 通知所有关注了我的微信用户
lisi 收到发送过来的消息
zhangsan 收到发送过来的消息
发起一个促销活动, 通知所有关注了我的微信用户
zhangsan 收到发送过来的消息
五. 观察者模式的使用场景
在软件系统中,当系统一方行为依赖另一方行为的变动时,就可以使用观察者模式松耦合联动双方,使得一方的变动可以通知到另一方对象,从而让另一方对象对此做出响应。
通过前面的分析与应用实例可知观察者模式适合以下几种情形。
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
六. 观察者模式的优缺点
优点
观察者和被观察者是抽象耦合的 建立了一套触发机制
缺点
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间 如果观察者和观察目标间有循环依赖,可能导致系统崩溃 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的
七. 扩展--发布订阅和观察者模式
在项目中经常会使用到mq, mq是发布订阅的方式. 那发布订阅是使用的观察者模式么? 其实, 发布订阅源于观察者模式, 但是又有所不同.
观察者模式,其实就是为了实现松耦合(loosely coupled)。
如上图, 观察者模式里面,changed()方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了
观察者模式和我们通常使用的mq有些类似? 也就是发布订阅. 详细参考这篇文章: zhuanlan.zhihu.com/p/51357583
发布订阅模式
很多人觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。
其实并不是。
在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。
互不相识?那他们如何交流?
答案是通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。
发布者只需告诉Broker,我要发的消息,topic是AAA;
订阅者只需告诉Broker,我要订阅topic是AAA的消息;
于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全不解耦的。
放一张图,对比一下这两个模式的区别:
总结
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
往更深层次讲:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
从使用层面上讲:
- 观察者模式,多用于单个应用内部
- 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件