大家好,你还在面向 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. 编程术语 All In One

    编程术语 All In One js 名词,术语 函数 函数签名 一个函数签名 (或类型签名,或方法签名) 定义了 函数 或 方法 的输入与输出. 一个签名可以包括: 参数 及参数的 类型 一个返回值 ...

  2. 神奇的数学学习网站 All In One

    神奇的数学学习网站 All In One magical math websites {{uploading-image-923797.png(uploading...)}} Math is Fun ...

  3. node.js & Unbuntu Linux & nvm & npm

    node.js & Unbuntu Linux & nvm & npm https://websiteforstudents.com/install-the-latest-no ...

  4. Node.js & BFF & FaaS

    Node.js & BFF & FaaS server https://github.com/PacktPublishing/Node.js-Web-Development-Fourt ...

  5. js in depth: closure function & curly function

    js in depth: closure function & curly function 闭包, 科里化 new js 构造函数 实例化, 不需要 new var num = new Ar ...

  6. NGK是如何运用IPFS分布式存储的?

    整个夏季,除了天气的火热,还有的火热莫过于IPFS挖矿这个领域了.IPFS的概念火热到,你可以看到到处都在卖IPFS矿机.那么,是什么原因导致IPFS这么火呢?在这之前,我们先了解一下什么是IPFS技 ...

  7. Chateau Renice酒庄 :忠于风味,尊重自然

    Chateau Renice酒庄(公司编号:20151318780)凭借优良的葡萄栽培传统.卓越的酿酒技术以及独特风味的葡萄酒,近年来葡萄酒畅销至全球. Chateau Renice酒庄将葡萄酒出口至 ...

  8. 12月15日BGV币行情分析

    今日,DeFi市场格外精彩.各主流概念币种走势出现了涨跌各半的两极态势.笔者认为,由于并没有总体可以利好DeFi市场的基本面因素,所以各DeFi概念币种的涨跌态势,还是与各自的基本面和技术面走势相关. ...

  9. 云原生系列5 容器化日志之EFK

    上图是EFK架构图,k8s环境下常见的日志采集方式. 日志需求 1 集中采集微服务的日志,可以根据请求id追踪到完整的日志: 2 统计请求接口的耗时,超出最长响应时间的,需要做报警,并针对性的进行调优 ...

  10. redis的两种持久化的机制,你真的了解么?

    redis提供了两种持久化的机制 RDB和AOF机制 RDB(redis Database):RDB保存某一个时间点之前的快照数据. AOF(Append-Only File):指所有的命令行记录以r ...