1、什么是观察者模式?

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

观察者模式(Observer Design Pattern):在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会得到通知并自动更新。

说人话:也叫发布订阅模式,能够很好的解耦一个对象改变,自动改变另一个对象这种情况。

2、观察者模式定义

①、Subject 被观察者

定义被观察者必须实现的职责, 它必须能够动态地增加、 取消观察者。 它一般是抽象类或者是实现类, 仅仅完成作为被观察者必须实现的职责: 管理观察者并通知观察者。

②、Observer观察者

观察者接收到消息后, 即进行update(更新方法) 操作, 对接收到的信息进行处理。

③、ConcreteSubject具体的被观察者

定义被观察者自己的业务逻辑, 同时定义对哪些事件进行通知。

④、ConcreteObserver具体的观察者

每个观察在接收到消息后的处理反应是不同, 各个观察者有自己的处理逻辑。

3、观察者模式通用代码

/**
* 观察者
*/
public interface Observer {
// 更新方法
void update();
}
/**
* 具体观察者
*/
public class ConcreteObserver implements Observer{
@Override
public void update() {
System.out.println("接受到信息,并进行处理");
}
}
/**
* 被观察者
*/
public abstract class Subject {
// 定义一个被观察者数组
private List<Observer> obsList = new ArrayList<>(); // 增加一个观察者
public void addObserver(Observer observer){
obsList.add(observer);
} // 删除一个观察者
public void delObserver(Observer observer){
obsList.remove(observer);
} // 通知所有观察者
public void notifyObservers(){
for (Observer observer : obsList){
observer.update();
}
}
}
/**
* 具体被观察者
*/
public class ConcreteSubject extends Subject{
// 具体的业务
public void doSomething(){
super.notifyObservers();
}
}
public class ObserverClient {

    public static void main(String[] args) {
// 创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
// 定义一个观察者
Observer observer = new ConcreteObserver();
// 观察者观察被观察者
subject.addObserver(observer);
subject.doSomething();
}
}

4、JDK 实现

在 JDK 的 java.util 包下,已经为我们提供了观察者模式的抽象实现,感兴趣的可以看看,内部逻辑其实和我们上面介绍的差不多。

观察者 java.util.Observer

被观察者 java.util.Observable

5、实例

用户进行注册,注册完成之后,会发一封欢迎邮件。

5.1 普通实现

public class UserController {

    public void register(String userName, String passWord){
// 1、根据用户名密码保存在数据库
Long userId = saveUser(userName, passWord);
// 2、如果上一步有结果则发送一封欢迎邮件
if(userId != null){
Mail.sendEmail(userId);
}
} public Long saveUser(String userName, String passWord){
return 1L;
}
}

上面的注册接口实现了两件事,注册和发送邮件,很明显违反了单一职责原则,但假设这个注册需求是不是经常变动的,这样写也没有什么问题,但是假如需求变动,比如不仅要发送邮件,还得发送短信,那还这样写,那register接口会变得很复杂。

那应该如何简化呢?没错,就是观察者模式。

5.2 观察者模式实现

我们直接套用 JDK 的实现。

import java.util.Observable;

/**
* 用户登录——被观察者
*/
public class UserControllerObservable extends Observable { public void register(String userName, String passWord){
// 1、根据用户名密码保存在数据库
Long userId = saveUser(userName, passWord);
// 2、如果上一步有结果则通知所有观察者
if(userId != null){
super.setChanged();
super.notifyObservers(userName);
}
} public Long saveUser(String userName, String passWord){
return 1L;
} }
import java.util.Observable;
import java.util.Observer; /**
* 发送邮件——观察者
*/
public class MailObserver implements Observer { @Override
public void update(Observable o, Object arg) {
System.out.println("发送邮件:" + arg + "欢迎你");
}
}
/**
* 发送手机短信——观察者
*/
public class SMSObserver implements Observer { @Override
public void update(Observable o, Object arg) {
System.out.println("发送短信:" + arg + "欢迎你");
}
}

测试:

public class UserClient {
public static void main(String[] args) {
UserControllerObservable observable = new UserControllerObservable();
observable.addObserver(new MailObserver());
observable.addObserver(new SMSObserver());
observable.register("张三","123");
}
}

通过观察者模式改写后,后面用户注册,就算在增加别的操作,我们也只需要增加一个观察者即可,而注册接口 register 不会有任何改动。

5.3 异步模式优化

在回到前面那张图:

注册之后进行的两步操作:发送邮件和发送短信,上面我们通过观察者模式改写之后,虽然流程很清晰,但是我们发现是顺序执行的,但其实这两步操作没有先后顺序,于是,我们可以改成异步模式,增加执行效率。

/**
* 发送邮件——观察者
*/
public class MailObserver implements Observer { private Executor executor = Executors.newFixedThreadPool(2); @Override
public void update(Observable o, Object arg) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("发送邮件:" + arg + "欢迎你");
}
}); }
}

5、EventBus

翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus 就是一个比较著名的 EventBus 框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。

PS:Google Guava 是一个特别好用的工具包,里面的代码也都实现的比较优雅,大家感兴趣的可以研究研究源码。

https://github.com/google/guava

下面我们以上面的例子来说明如何使用 EventBus:

①、导如 Guava 包

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>

②、具体代码如下:

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus; import java.util.List;
import java.util.concurrent.Executors; public class UserController {
private EventBus eventBus; public UserController(){
eventBus = new AsyncEventBus(Executors.newFixedThreadPool(2));
} /**
* 注意:泛型参数是 Object,而不是接口 Observer
* @param observerList
*/
public void setObserverList(List<Object> observerList){
for(Object observer : observerList){
eventBus.register(observer);
}
} public void register(String userName, String passWord){
// 1、根据用户名密码保存在数据库
Long userId = saveUser(userName, passWord);
// 2、如果上一步有结果则通知所有观察者
if(userId != null){
eventBus.post(userName);
}
} public Long saveUser(String userName, String passWord){
return 1L;
}
}
import com.google.common.eventbus.Subscribe;

/**
* 发送邮件——观察者
*/
public class MailObserver{ @Subscribe
public void sendMail(String userName) {
System.out.println("发送邮件:" + userName + "欢迎你");
}
}
import com.google.common.eventbus.Subscribe;

/**
* 发送手机短信——观察者
*/
public class SMSObserver{ @Subscribe
public void sendSMS(String userName) {
System.out.println("发送短信:" + userName + "欢迎你");
}
}

测试:

public class EventBusClient {
public static void main(String[] args) {
UserController userController = new UserController();
List<Object> observerList = new ArrayList<>();
observerList.add(new MailObserver());
observerList.add(new SMSObserver());
userController.setObserverList(observerList);
userController.register("张三","123");
}
}

利用 EventBus 框架实现的观察者模式,跟从零开始编写的观察者模式相比,从大的流程上来说,实现思路大致一样,都需要定义 Observer,并且通过 register() 函数注册 Observer,也都需要通过调用某个函数(比如,EventBus 中的 post() 函数)来给 Observer 发送消息(在 EventBus 中消息被称作事件 event)。但在实现细节方面,它们又有些区别。基于 EventBus,我们不需要定义 Observer 接口,任意类型的对象都可以注册到 EventBus 中,通过 @Subscribe 注解来标明类中哪个函数可以接收被观察者发送的消息。

6、观察者模式优点

①、观察者和被观察者之间是抽象耦合

不管是增加观察者还是被观察者都非常容易扩展,在系统扩展方面会得心应手。

②、建立一套触发机制

被观察者变化引起观察者自动变化。但是需要注意的是,一个被观察者,多个观察者,Java的消息通知默认是顺序执行的,如果一个观察者卡住,会导致整个流程卡住,这就是同步阻塞。

所以实际开发中没有先后顺序的考虑使用异步,异步非阻塞除了能够实现代码解耦,还能充分利用硬件资源,提高代码的执行效率。

另外还有进程间的观察者模式,通常基于消息队列来实现,用于实现不同进程间的观察者和被观察者之间的交互。

7、观察者模式应用场景

①、关联行为场景。

②、事件多级触发场景。

③、跨系统的消息交换场景, 如消息队列的处理机制。

Java设计模式之(十二)——观察者模式的更多相关文章

  1. Java设计模式(十二) 策略模式

    原创文章,同步发自作者个人博客,http://www.jasongj.com/design_pattern/strategy/ 策略模式介绍 策略模式定义 策略模式(Strategy Pattern) ...

  2. Java设计模式之十二 ---- 备忘录模式和状态模式

    前言 在上一篇中我们学习了行为型模式的策略模式(Strategy Pattern)和模板模式(Template Pattern).本篇则来学习下行为型模式的两个模式,备忘录模式(Memento Pat ...

  3. Java 设计模式系列(二十)状态模式

    Java 设计模式系列(二十)状态模式 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式.状态模式允许一个对象在其内部状态改变的时候改 ...

  4. 桥接模式 桥梁模式 bridge 结构型 设计模式(十二)

      桥接模式Bridge   Bridge 意为桥梁,桥接模式的作用就像桥梁一样,用于把两件事物连接起来   意图 将抽象部分与他的实现部分进行分离,使得他们都可以独立的发展.  意图解析 依赖倒置原 ...

  5. Java 设计模式系列(二二)责任链模式

    Java 设计模式系列(二二)责任链模式 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求 ...

  6. 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条

    http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...

  7. Java进阶(三十二) HttpClient使用详解

    Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...

  8. Java进阶(五十二)利用LOG4J生成服务日志

    Java进阶(五十二)利用LOG4J生成服务日志 前言 由于论文写作需求,需要进行流程挖掘.前提是需要有真实的事件日志数据.真实的事件日志数据可以用来发现.监控和提升业务流程. 为了获得真实的事件日志 ...

  9. Java设计模式(20)观察者模式(Observer模式)

    Java深入到一定程度,就不可避免的碰到设计模式(design pattern)这一概念,了解设计模式,将使自己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广泛,遵循 ...

  10. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

随机推荐

  1. Vue Router 常见问题(push报错、push重复路由刷新)

    Vue Router 常见问题 用于记录工作遇到的Vue Router bug及常用方案 router.push报错,Avoided redundant navigation to current l ...

  2. Pytorch——张量 Tensors

    张量 Tensors 1.torch.is_tensor torch.is_tensor(obj) 用法:判断是否为张量,如果是 pytorch 张量,则返回 True. 参数:obj (Object ...

  3. Java:LinkedHashMap类小记

    Java:LinkedHashMap类小记 对 Java 中的 LinkedHashMap类,做一个微不足道的小小小小记 概述 public class LinkedHashMap<K,V> ...

  4. 【二食堂】Beta - 发布声明

    Beta - 发布声明 新功能 在Beta阶段,图谱方面的新功能有:自定义关系的添加与删除.实体查找.实体名称的修改.实体之间关系的修改.新增了项目创建与删除功能,此外还增加了好友系统,可以实现好友的 ...

  5. Scrum Meeting 0609

    零.说明 日期:2021-6-9 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 困难 qsy PM&前端 完成前端功能 ...

  6. UltraSoft - Alpha - Scrum Meeting 1

    Date: Apr 06th, 2020. 会议内容为讨论功能规格书和技术规格书的撰写. Scrum 情况汇报 进度情况 组员 负责 昨日进度 后两日任务 CookieLau PM.后端 进行Djan ...

  7. 聊聊 Kubernetes Pod or Namespace 卡在 Terminating 状态的场景

    这个话题,想必玩过kubernetes的同学当不陌生,我会分Pod和Namespace分别来谈. 开门见山,为什么Pod会卡在Terminationg状态? 一句话,本质是API Server虽然标记 ...

  8. HTML+CSS基础(HTML篇)

    引言 在日常开发Android中,很多时候会遇到和WebView打交道,对CSS HTML JS不是很清楚的话是完不成一些功能的,本篇开始学习HTML,文章的主要内容是总结了慕课网中,HTML+CSS ...

  9. 修改 oracle 数据库的 sys 账号密码,ERROR at line 1: ORA-01034: ORACLE not available

    挺久没有登录的 oracle 数据库,因为公司要求加固密码,登录后修改失败 1.启动数据库的同时启动控制文件.数据文件,提示:cannot mount database in EXCLUSIVE mo ...

  10. .NET 开源工作流: Slickflow流程引擎高级开发(九) -- 条件事件模式解释及应用

    前言:在流程流转过程中,有时候需要条件模式的支持,这样可以使得流程流转更加灵活多变.比如在业务变量满足一定的条件时,可以启动特定配置的流程(或者位于主流程内部的子流程).本文主要描述条件启动和条件中间 ...