大家好,你还在面向 for 循环编程吗?

还有谁不会用观察者模式吗?

本篇带来《观察者模式》理论及实战~

什么是观察者模式?

观察者模式(Observer Pattern)定义了对象间的一种一对多的依赖关系,这样只要一个对象的状态发生改变,其依赖的所有相关对象都会得到通知并自动更新。

在观察者模式中,发生改变的对象叫做观察目标,而被通知更新的对象称为观察者,一个观察目标对应多个观察者,观察者一般是一个列表集合,可以根据需要动态增加和删除,易于扩展。

使用观察者模式的优点在于观察目标和观察者之间是抽象松耦合关系,降低了两者之间的耦合关系。

 

发布-订阅模式

观察者模式很多地方也叫发布-订阅模式(Publish/Subscribe),其实也可以这么理解,不过两者之间还是略有不同。

观察者模式中的观察者是直接绑定观察目标,观察目标要维护一套观察者列表,两者是有一个基于接口的组合依赖关系的,所以说观察者模式虽然是松耦合的,但并不是完全解耦的。

发布-订阅模式中的发布者和订阅者两者并没有任何联系,发布者通过中间方发布一个主题(Topic),订阅者通过中间方(调度中心)订阅一个主题(Topic),发布者状态的变更也并不会直接通知订阅者,而要通过中间方进行通知,或者订阅者自行从中间方拉取,所以说发布-订阅模式是完全解耦的。

一图搞懂它们的关系:

观察者模式和订阅发布模式的区别

从图片看两者是有差别的,统一都叫观察者模式,也没毛病。

 

观察者模式轮子

因观察者模式应用比较广泛,所以 JDK 工具包从 1.0 版本里面自带了观察者模式模板套装,我们根据其模板很方便就能实现观察者模式,不需要再重复造轮子了。

观察者目标类:

java.util.Observable

里面两个最重要的变量:

  • changed:观察目标状态是否变更,默认为:false;

  • obs:观察者列表(observers),一个线程安全的列表集合:Vector,默认为空集合;

里面的重要的方法都是和观察目标状态和观察者相关的,一看就清楚,这里就不介绍了。

观察者接口:

java.util.Observable

public interface Observer {/*** This method is called whenever the observed object is changed. An* application calls an <tt>Observable</tt> object's* <code>notifyObservers</code> method to have all the object's* observers notified of the change.** @param   o     the observable object.* @param   arg   an argument passed to the <code>notifyObservers</code>*                 method.*/void update(Observable o, Object arg);
}

观察者接口只有一个 update 方法,用来通知观察者自己更新。

 

观察者模式实战

OK,知道了 JDK 自带了这两个东东,现在就来实现一个简单的观察者模式的应用场景,模拟公众号文章推送,观察目标是栈长我,观察者是你们大家,我在公众号栈推一篇文章,你们都能接收到更新通知并能阅读。

新增观察目标类:

import lombok.Getter;import java.util.Observable;/*** 观察目标:栈长*/
@Getter
public class JavaStackObservable extends Observable {private String article;/*** 发表文章* @param article*/public void publish(String article){// 发表文章this.article = article;// 改变状态this.setChanged();// 通知所有观察者this.notifyObservers();}}

观察目标的逻辑是先发表文章,再改变观察目标的状态,再通知所有观察者。

我们来重点看 notifyObservers 方法的源码:

先获取同步锁,判断状态是否更新,如已更新则清空观察目标状态,然后再使用 for 循环遍历所有观察者,一一调用观察者的更新方法通知观察者更新。

新增观察者类:

import lombok.NonNull;
import lombok.RequiredArgsConstructor;import java.util.Observable;
import java.util.Observer;/*** 观察者:读者粉丝*/
@RequiredArgsConstructor
public class ReaderObserver implements Observer {@NonNullprivate String name;private String article;@Overridepublic void update(Observable o, Object arg) {// 更新文章updateArticle(o);}private void updateArticle(Observable o) {JavaStackObservable javaStackObservable = (JavaStackObservable) o;this.article = javaStackObservable.getArticle();System.out.printf("我是读者:%s,文章已更新为:%s\n", this.name, this.article);}}

观察者的逻辑是获取到观察者目标实例对象,然后再用观察目标对象的文章信息更新为自己的文章信息,最后输出某某某的文章已更新。

观察者只要实现 Observer 这个接口的 update 方法即可,用于观察目标进行调用通知。

观察目标和观察者类结构图如下:

新增测试类:

/*** 观察者:读者粉丝*/
public class ObserverTest {public static void main(String[] args) {// 创建一个观察目标JavaStackObservable javaStackObservable = new JavaStackObservable();// 添加观察者javaStackObservable.addObserver(new ReaderObserver("小明"));javaStackObservable.addObserver(new ReaderObserver("小张"));javaStackObservable.addObserver(new ReaderObserver("小爱"));// 发表文章javaStackObservable.publish("什么是观察者模式?");}}

观察目标、观察者的创建并没有先后顺序要求,重点是发表文章通知观察者之前,观察目标要添加观察者列表这一步不能少。

输出结果:

通过这个简单的文章推送实践,大家应该对观察者模式有一个基本的认知了,在实际工作当中也可以有很多场景拿去用,就一对多的依赖关系都可以考虑使用观察者模式。

 

总结

不容易啊,陆陆续续又肝了大半天,你学会观察者模式了吗?

观察者模式的优点是为了给观察目标和观察者解耦,而缺点也很明显,从上面的例子也可以看出,如果观察者对象太多的话,有可能会造成内存泄露。

另外,从性能上面考虑,所有观察者的更新都是在一个循环中排队进行的,所以观察者的更新操作可以考虑做成线程异步(或者可以使用线程池)的方式,以提升整体效率。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

别再面向 for 循环编程了,JDK 自带的观察者模式就很香!相关推荐

  1. Spring 详解(三):AOP 面向切面的编程

    AOP即面向切面编程,它通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型. ...

  2. 如何用ABP框架快速完成项目(面向项目交付编程面向客户编程篇) - 广州.net微软技术俱乐部12月份活动报名帖...

    这是广州.net微软技术俱乐部12月份活动报名帖.此帖会持续更新. 活动课程标题是:如何用ABP框架快速完成项目(面向项目交付编程面向客户编程篇) 这是内容大纲: ABP框架简介(这里会聊聊.net真 ...

  3. spring 面向接口编程_Spring面向方面的编程

    spring 面向接口编程 介绍 在理想的面向对象系统中,我们希望将每个对象设计为执行一项特定任务. 但是,除了执行其主要任务之外,对象还执行被动任务,例如日志记录,事务,安全性,缓存等.这些被动活动 ...

  4. Spring面向方面的编程

    介绍 在理想的面向对象系统中,我们希望将每个对象设计为执行一项特定任务. 但是,除了执行其主要任务外,对象还执行被动任务,例如日志记录,事务,安全性,缓存等.这些被动活动是必需的,但不是业务逻辑的一部 ...

  5. 自上而下面向能力的编程思想

    前言: 行业里对码农的描述有很多种抬头:研发.开发.工程师等,我个人觉得描述最准确的应该是"工程师",因为软件产品的开发本来就是个工程学的问题,只不过软件工程和土木工程的施工过程是 ...

  6. C++,一球从某一高度落下(整数,单位是米),每次落地后跳回原来高度的一半,再落下。编程计算球在第5次落地时,共经过多少米?第5次反弹多高?【多组输入】

    一球从某一高度落下(整数,单位是米),每次落地后跳回原来高度的一半,再落下.编程计算球在第5次落地时,共经过多少米?第5次反弹多高?[多组输入] 输入格式 输入一个整数h0,表示球的初始高度 输出格式 ...

  7. JAVA面向接口的编程思想与具体实现

    面向对象设计里有一点大家已基本形成共识,就是面向接口编程,我想大多数人对这个是没有什么觉得需要怀疑的.         问题是在实际的项目开发中我们是怎么体现的呢? 难道就是每一个实现都提供一个接口就 ...

  8. 【翻译】粉碎Gadgets:使用就地代码随机化防御面向返回的编程——Smashing the Gadgets: Hindering Return-Oriented Programming...

    粉碎Gadgets:使用就地代码随机化防御面向返回的编程 [文章为google-translate的直译结果,最近暂时没有时间修改翻译内容.google-translate的翻译结果中有很多明显的错误 ...

  9. Cadence——面向资源的编程

    软件工程师们经常使用"所有权"一词,用来表示,某块代码负责管理某种数据结构或系统资源.这种隐喻在编程环境中最为普遍,在这种环境中,内存管理并非是从程序成员那里抽象出来的,所谓的代码 ...

最新文章

  1. 教程 | 算法太多挑花眼?教你如何选择正确的机器学习算法
  2. oracle decode 01427,dbms_hm.run_check遇到ORA-00604、ORA-01427
  3. 基于 MATLAB 的 PCM 编码解码实现
  4. 降低百倍时间步,精度媲美传统神经网络:上交等机构提出ANN-SNN转换框架
  5. HDU - 4035 Maze(概率dp)
  6. JAR文件句柄:烦恼后清理!
  7. 【记忆化搜索】bzoj3208 花神的秒题计划Ⅰ
  8. 友元关系可以继承_私生子也有继承权!非婚生子女的继承关系如何认定?
  9. 在搜索框自动输入文本_【Zotero文档翻译】管理篇:搜索
  10. C#编码规范2[转]
  11. 在windows server 2003服务器上提供NTP时间同步服务
  12. 【医学图像分割】基于matlab磁共振成像 (MRI) 数值模拟平台【含Matlab源码 826期】
  13. 决策树识别MNIST数据集
  14. java 生成 顺序 uuid_Java 生成有序 UUID
  15. 怎么打开服务器的xls文件,xls是什么文件格式?.xls文件打开方法
  16. 苹果xs还原卡在进度条怎么关机
  17. strip()函数用法简介
  18. Google AppSheet: 无需编程构建零代码应用
  19. iPad,iPad Pro和iPad Mini有什么区别?
  20. 1.3 电功率和能量

热门文章

  1. js方法写在html中,在js中写html代码怎么写
  2. if laytpl 非_Layui-神奇的layui.laytpl
  3. python中计算列表的平均值_如何计算python中元组列表的平均值?
  4. android 抓log暗码,「有用功」强大的安卓暗码命令 你都知道吗?
  5. TraceView(profile) and Systrace
  6. springboot gateway post body 为空_Spring Boot常见属性以及问题总结
  7. KVM虚拟机搭建增量镜像(一个基本镜像拷贝成无数多个子镜像)
  8. Python redis的订阅发布机制(publish、pubsub)
  9. 主机与虚拟机桥接出现ping不通解决方案
  10. Python函数中参数* 和 ** 的区别