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. 简单的 Go 入门教程

    Go(又称 Golang )是 Google 开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言 Docker 和 Kubernetes 都是使用 Go 进行开发的,这几年 Go 越来 ...

  2. 用C++实现的数独解题程序 SudokuSolver 2.4 及实例分析

    SudokuSolver 2.4 程序实现 本次版本实现了 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析 里发现的第三个不完全收缩 grp 算法 thirdGreenWor ...

  3. ArrayList和Vector

    ArrayList和Vector ArrayList ArrayList的注意实现 1.ArrayList可以加入null,并且多个 2.ArrayList是由数组来实现数据存储的 3.ArrayLi ...

  4. SpringCloud微服务实战——搭建企业级开发框架(四):集成SpringCloud+SpringBoot

    1.在GitEgg工程的根目录,最上级父pom.xml文件中引入需要依赖的库及Maven插件,设置编码方式: <!--?xml version="1.0" encoding= ...

  5. 新手小白在github上部署一个项目

    新手小白在github上部署一个项目 一. 注册github账号 github地址:https://www.github.com/ 二.下载安装Git 地址:https://git-scm.com/d ...

  6. Github 29K Star的开源对象存储方案——Minio入门宝典

    对象存储不是什么新技术了,但是从来都没有被替代掉.为什么?在这个大数据发展迅速地时代,数据已经不单单是简单的文本数据了,每天有大量的图片,视频数据产生,在短视频火爆的今天,这个数量还在增加.有数据表明 ...

  7. Alpha阶段初始任务分配

    项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目-计划-Alpha阶段说明书 一.Alpha阶段总体规划 进行服务器相关部署 进行开发相关技术学习 ...

  8. [对对子队]Beta阶段项目展示博客

    Beta阶段项目展示博客 1 团队成员的简介和个人博客地址 成员 头像 岗位 博客 个人介绍 黄贤昊 PM 17373253 喜欢玩游戏和做游戏,项目经验基本都和游戏相关,擅长摸鱼,偶尔敬业. 吴桐雨 ...

  9. git为单独的仓库设置提交的用户名

    在我们平时的学习中可能有这么一种需求,在公司进行开发的时候,一般会参与多个项目的开发,而项目提交代码时,一般请求情况下提供的用户都是同一个,而我们为了方便可能会使用全局进行git 用户名的配置.但是空 ...

  10. USB OTG原理和 ID 检测原理

    OTG 检测的原理是: USB OTG标准在完全兼容USB2.0标准的基础上,增添了 电源管理(节省功耗)功能,它允许设备既可作为主机,也可作为外设操作(两用OTG).USB OTG技术可实现没有主机 ...