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. centos7谷歌chrome内网部署演示

    上传需要的包,注释网关创建内网环境 [root@localhost ~]# ls anaconda-ks.cfg chrome mcw4 mcw4.tar.gz mcwchromerpm.tar.gz ...

  2. Perl操作excel2007的模块

    详细版:https://www.jianshu.com/p/84bda53827c8 第一种方法: 读写excel2007文档的perl模块: Spreadsheet::XLSX(读)和Spreads ...

  3. 网页常用的css特效让互动留住客户

    一般网站如果制作按钮,多做一些互动,可以让客户获得更好的体验. 例如鼠标滑过按钮,让背景颜色从左往右滑出来(或者从右往左都可以): <a target="_blank" hr ...

  4. WPF中的命令(Command)

    这节来讲一下WPF中的命令(Command)的使用. [认识Command] 我们之前说过,WPF本身就为我们提供了一个基础的MVVM框架,本节要讲的命令就是其中一环,通过在ViewModel中声明命 ...

  5. 写了10000条Airtest截图脚本总结出来的截图经验,赶紧收藏!

    前言 今天想先给大家分享1个小白用户的Airtest从入门到放弃的故事: 小A是一个自动化的小白,在逛测试论坛的时候,偶然间发现了Airtest这个基于图像识别的UI自动化框架. 出于好奇,小A试用了 ...

  6. [对对子队]会议记录4.19(Scrum Meeting10)

    今天已完成的工作 何瑞 ​ 工作内容:搭建第2关,基本完成第3关 ​ 相关issue:搭建关卡2.3 ​ 相关签入:4.19签入1 4.19签入2 刘子航 ​ 工作内容:完成关卡选择界面的设计图 ​ ...

  7. [技术博客] 软工-Ruby on Rails 后端开发总结分享

    [技术博客] 软工-Ruby on Rails 后端开发总结分享 在这次软件编写中,我们的后端使用了Ruby on Rails (RoR)框架. Rails框架是用Ruby编写的.这意味着当我们为Ru ...

  8. [技术博客] Django中文件的保存与访问

    [技术博客] Django中文件的保存与访问 在TextMarking项目开发中,数据库需要保存用户上传的文本文档. 原型设计:用户点击上传文本->保存文本->文本发送到后端保存为文件. ...

  9. 算法:Z字型(Zigzag)编排

    问题:给定 n 行和 m 列的二维数组矩阵.如图所示,以 ZIG-ZAG 方式打印此矩阵. 从对称的角度来看,通过反复施加滑行反射可以从简单的图案如线段产生规则的之字形. 主要思想:算法从(0, 0) ...

  10. 转载: XILINX GT的基本概念

    https://zhuanlan.zhihu.com/p/46052855 本来写了一篇关于高速收发器的初步调试方案的介绍,给出一些遇到问题时初步的调试建议.但是发现其中涉及到很多概念.逐一解释会导致 ...