设计模式实战-观察者模式,你知道发布订阅怎么实现吗
1、定义
观察者模式(Observer Pattern)也称发布订阅模式。
观察者模式的英文定义如下:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
意思是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
以生活中的例子来说,就像我们订阅报纸一样,每天有多少人订阅,当有新报纸发布的时候,就会有多少人收到新发布的报纸,这种模式就是订阅 - 发布模式,而报社和订阅者就满足定义中说是的,一对多的依赖关系。
“观察者模式” 这个词可能不太好理解,但如果用 “发布 — 订阅模式” 来替代的话,就相对好理解一些。
小贴士:本文会采用” 观察者模式 “来编写内容,但读者可以用” 发布 - 订阅模式 “来理解本文的内容,两者所说的是同一种模式。
2、组成角色
观察者模式包含如下角色:
- 抽象主题(Subject)角色:该角色又称为 “发布者” 或” 被观察者 “,可以增加和删除观察者对象;
- 具体主题(Concrete Subject)角色:该角色又称为 “具体发布者” 或 “具体被观察者”,它将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过(关联了观察关系)的观察者发出通知;
- 抽象观察者(Observer)角色:该角色又称为 “订阅者”,定义一个接收通知的接口,在得到主题的通知时更新自己;
- 具体观察者(Concrete Observer)角色:该角色又称为 “具体订阅者”,它会实现一个接收通知的方法,用来使自身的状态与主题的状态相协调。
角色之间的 UML 关系图如下:
3、观察者模式代码实现
3.1 抽象主题(发布者接口)
/**
* 抽象主题(发布者接口)
*/
interface Subject {
// 添加观察者(订阅者)
public void attach(Observer o);
// 删除观察者(订阅者)
public void detach(Observer o);
// 通知所有观察者(订阅者)
public void notifyObservers();
}
3.2 具体主题(发布者)
/**
* 具体主题(发布者)
*/
class ConcreteSubject implements Subject {
// 存放观察者(订阅者)
private List<Observer> list = new ArrayList<Observer>();
@Override
public void attach(Observer o) {
// 添加观察者(订阅者)
list.add(o);
}
@Override
public void detach(Observer o) {
// 删除观察者(订阅者)
list.remove(o);
}
@Override
public void notifyObservers() {
// 通知所有观察者(订阅者)
for (Observer o : list) {
o.update();
}
}
/**
* 通知方法
*/
public void change() {
this.notifyObservers();
}
}
3.3 抽象观察者(订阅者接口)
/**
* 抽象观察者(订阅者接口)
*/
interface Observer {
public void update();
}
3.4 具体观察者(订阅者)
/**
* 具体观察者(订阅者)
*/
class ConcreteObserver implements Observer {
@Override
public void update() {
// 主题有更新之后,执行的具体订阅(通知)方法
System.out.println("我收到了通知~");
}
}
3.5 客户端(调用)
/**
* 观察者模式
*/
public class Client {
public static void main(String[] args) {
// 创建主题(发布者)
ConcreteSubject subject = new ConcreteSubject();
// 创建观察者(订阅者)
Observer observer = new ConcreteObserver();
// 关联订阅
subject.attach(observer);
// 改变主题(发布者)状态,发送通知
subject.change();
}
}
程序执行结果如下:
我收到了通知~
从以上代码可以看出,当主题(ConcreteSubject)的状态发生变化时,就会触发通知方法,通知方法会通知所有的观察者对象(ConcreteObserver),这样就完成了整个发布 — 订阅的过程。
4、优缺点
观察者模式的优点:
- 观察者和被观察者之间,实现了抽象耦合。被观察者角色所知道的只是一个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展;
- 此模式为广播模式,所有的观察者只需要订阅相应的主题,就能收到此主题下的所有广播。
观察者模式的缺点:
- 观察者只知道被观察者会发生变化,但不知道何时会发生变化;
- 如果主题之间有循环依赖,会导致系统崩溃,所以在使用时要特别注意此种情况;
- 如果有很多个观察者,则每个通知会比较耗时。
5、应用场景
使用观察模式的典型应用场景如下:
- 关联行为的场景,例如,在一个系统中,如果用户完善了个人资料,就会增加积分、添加日志、开放一些功能权限等,就比较适合用观察者模式;
- 消息队列,例如,需要隔离发布者和订阅者,需要处理一对多关系的时候。
6、使用实例
以生活中的读者订阅为例,假设,读者 A 和 读者 B 订阅了某平台的图书,当有新的图书发布时就会给两位读者发送图书,实现代码如下。
6.1 读者接口和实现类
/**
* 读者接口(订阅接口)
*/
interface IReader {
public void update(String bookName);
}
/**
* 读者类(订阅者)
*/
class Reader implements IReader {
private String name;
public Reader(String name) {
this.name = name;
}
@Override
public void update(String bookName) {
System.out.println(name + "-收到了图书:" + bookName);
}
}
6.2 平台接口和实现类
/**
* 平台接口(发布方接口)
*/
interface IPlatform {
public void attach(IReader reader);
public void detach(IReader reader);
public void notifyObservers(String bookName);
}
/**
* 具体平台类(发布方)
*/
class Platform implements IPlatform {
// 存放读者(订阅者)
private List<IReader> list = new ArrayList();
@Override
public void attach(IReader reader) {
// 添加读者(订阅者)
list.add(reader);
}
@Override
public void detach(IReader reader) {
// 删除读者(订阅者)
list.remove(reader);
}
@Override
public void notifyObservers(String bookName) {
// 通知所有读者(订阅者)
for (IReader reader : list) {
reader.update(bookName);
}
}
/**
* 通知方法
*/
public void change(String bookName) {
this.notifyObservers(bookName);
}
}
6.3 客户端(调用)
public class Client {
public static void main(String[] args) {
// 创建图书平台(发布者)
Platform platform = new Platform();
// 创建读者 A(订阅者)
Reader reader = new Reader("A");
// 读者 A 订阅图书通知
platform.attach(reader);
// 创建读者 (订阅者)
Reader reader2 = new Reader("B");
// 读者 B 订阅图书通知
platform.attach(reader2);
platform.change("《Java面试全解析》");
}
}
程序执行结果如下:
A - 收到了图书:《Java 面试全解析》 B - 收到了图书:《Java 面试全解析》
7、总结
观察者模式就是一个发布者对应多个订阅者的模式,发布者对应的角色就是主题(Subject),而订阅者对应的角色就是观察者(Observer),只要订阅者订阅了发布者(对象),当发布者的状态发生变化时,就会通知所有的订阅者。
- 史上最强Spring mvc入门
- 上边半透明的效果并且显示的是上一页的内容
- Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
- android自定义view实现公章效果
- ios app url scheme跳转到淘宝商品详情页 唤醒app
- ThreadPoolExecutor运行机制
- Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
- UIPickView的简单使用
- java开发中几种常见的线程池
- 传统多线程之前如何共享数据
- Spring Cloud构建微服务架构:服务容错保护(Hystrix断路器)【Dalston版】
- 调整渐变下降的学习率
- 多线程之传统多线程
- ios 常用的正则表达式(手机号邮箱md5加密验证空字符串等)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Java 报错信息 Error during JavaScript execution
- 记录一次SpringBoot Autowired为null的错误
- Collection接口-常用方法
- Qt开源作品34-qwt无需插件源码
- Qt开源作品35-秘钥生成器
- Qt开源作品36-程序守护进程
- Qt开源作品37-网络中转服务器
- Qt编写安防视频监控系统27-GPU显示
- Qt编写安防视频监控系统28-摄像机点位
- Qt编写安防视频监控系统29-掉线重连
- Qt编写安防视频监控系统30-GPS运动轨迹
- Qt编写安防视频监控系统31-onvif设备搜索
- Qt编写安防视频监控系统32-onvif信息获取
- Qt编写安防视频监控系统33-onvif云台控制
- Qt编写安防视频监控系统34-onvif事件订阅