大家好,你还在面向 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 自带了这两个东东,现在就来实现一个简单的观察者模式的应用场景,模拟公众号文章推送,观察目标是栈长我,观察者是你们大家,我在公众号Java技术栈推一篇文章,你们都能接收到更新通知并能阅读。

新增观察目标类:

import lombok.Getter;

import java.util.Observable;

/**
* 观察目标:栈长
* 来源微信公众号:Java技术栈
*/
@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; /**
* 观察者:读者粉丝
* 来源微信公众号:Java技术栈
*/
@RequiredArgsConstructor
public class ReaderObserver implements Observer { @NonNull
private String name; private String article; @Override
public 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 方法即可,用于观察目标进行调用通知。

本节教程所有实战源码已上传到这个仓库:https://github.com/javastacks/javastack

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

新增测试类:

/**
* 观察者:读者粉丝
* 来源微信公众号:Java技术栈
*/
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("什么是观察者模式?");
} }

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

输出结果:

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

总结

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

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

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

本节教程所有实战源码已上传到这个仓库:

https://github.com/javastacks/javastack

好了,今天的分享就到这里了,后面栈长我会更新其他设计模式的实战文章,公众号Java技术栈第一时间推送。Java技术栈《设计模式》系列文章陆续更新中,请大家持续关注哦!

最后,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。

版权申明:本文系公众号 "Java技术栈" 原创,原创实属不易,转载、引用本文内容请注明出处,禁止抄袭、洗稿,请自重,尊重他人劳动成果和知识产权。

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

  1. 别再面向 for 循环编程了,Spring 自带的观察者模式就很香!

    上一篇:JDK 自带的观察者模式就很香! 前段时间栈长给大家分享了什么是观察者模式,以及在 JDK 中如何实现观察者模式,现在都是 Spring 的天下了,今天就再分享下如何在 Spring/ Spr ...

  2. JDK 自带的观察者模式源码分析以及和自定义实现的取舍

    前言 总的结论就是:不推荐使用JDK自带的观察者API,而是自定义实现,但是可以借鉴其好的思想. java.util.Observer 接口源码分析 该接口十分简单,是各个观察者需要实现的接口 pac ...

  3. 【java高级编程】jdk自带事件模型编程接口

    事件类 java.util.EventObject java.beans.PropertyChangeEvent 事件监听接口 java.util.EventListener java.beans.P ...

  4. GO语言的进阶之路-面向过程式编程

    GO语言的进阶之路-面向过程式编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们在用Golang写一个小程序的时候,未免会在多个地方调用同一块代码,这个时候如何优化你的代码呢 ...

  5. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)  (1)框架搭建    (2):数据 ...

  6. 老李推荐:第14章1节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-面向控件编程VS面向坐标编程

    老李推荐:第14章1节<MonkeyRunner源码剖析> HierarchyViewer实现原理-面向控件编程VS面向坐标编程   poptest是国内唯一一家培养测试开发工程师的培训机 ...

  7. 【C++系列小结】面向过程的编程风格

    前言 编程语言有面向过程和面向对象之分,因此编程风格也有所谓的面向过程的编程和面向对象的编程,并且语言的性质不会限制编程的风格. 这里主要说一以下向过程的编程. "面向过程"(Pr ...

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

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

  9. JS高级---体会面向对象和面向过程的编程思想

    体会面向对象和面向过程的编程思想 ChangeStyle是自定义的构造函数,再通过原型添加方法的函数. 实例化对象,导入json参数,和创建cs,调用原型添加的方法函数 过渡,先熟悉记忆 <!D ...

随机推荐

  1. how to measure function performance in javascript

    how to measure function performance in javascript Performance API Performance Timeline API Navigatio ...

  2. Flutter for Desktop

    Flutter for Desktop https://flutter.dev/desktop https://github.com/flutter/flutter/wiki/Desktop-shel ...

  3. UI 素材网站

    UI 素材网站 UI 设计师网站 国外: dribble.behance.Pinteres 国内: 优设.站酷.UI中国.花瓣 https://design.google/ https://dribb ...

  4. SHON WEBB:太怀念过去的人,往往走不远

    太怀念过去的人,最后都怎么样?近日,星盟审批官SHON WEBB先生给出了答案,他认为,如果一个人太怀念过去,怀念过去自己所有的荣耀,而轻视现在的任何工作,那他往往走不远. SHON WEBB先生讲到 ...

  5. JDK源码阅读-FileDescriptor

    本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...

  6. 进阶高阶IoT架构-教你如何简单实现一个消息队列

    前言 消息队列是软件系统领域用来实现系统间通信最广泛的中间件.基于消息队列的方式是指由应用中的某个系统负责发送消息,由关心这条消息的相关系统负责接收消息,并在收到消息后进行各自系统内的业务处理.消息可 ...

  7. synchronized语法

    synchronized( ){ } synchronized 关键字是加锁的意思,用它来修饰方法就表示给该方法加了锁,从而达到线程同步的效果;用它来修饰代码块就表示给该代码块加了锁,从而达到线程同步 ...

  8. Java常用类:Scanner类

    一.简介 java.util.Scanner是Java5的新特征,我们可以通过Scanner类来获取用户的输入. 二.创建对象 示例: Scanner scanner = new Scanner(Sy ...

  9. 翻译:《实用的 Python 编程》02_07_Objects

    目录 | 上一节 (2.6 列表推导式) | 下一节 (3 从程序组织) 2.7 对象 本节介绍有关 Python 内部对象模型的更多详细信息,并讨论一些与内存管理,拷贝和类型检查有关的问题. 赋值 ...

  10. 用量子计算模拟器ProjectQ生成随机数,并用pytest进行单元测试与覆盖率测试

    技术背景 本文中主要包含有三个领域的知识点:随机数的应用.量子计算模拟产生随机数与基于pytest框架的单元测试与覆盖率测试,这里先简单分别介绍一下背景知识. 随机数的应用 在上一篇介绍量子态模拟采样 ...