Java设计模式之(十二)——观察者模式
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 是一个特别好用的工具包,里面的代码也都实现的比较优雅,大家感兴趣的可以研究研究源码。
下面我们以上面的例子来说明如何使用 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设计模式之(十二)——观察者模式的更多相关文章
- Java设计模式(十二) 策略模式
原创文章,同步发自作者个人博客,http://www.jasongj.com/design_pattern/strategy/ 策略模式介绍 策略模式定义 策略模式(Strategy Pattern) ...
- Java设计模式之十二 ---- 备忘录模式和状态模式
前言 在上一篇中我们学习了行为型模式的策略模式(Strategy Pattern)和模板模式(Template Pattern).本篇则来学习下行为型模式的两个模式,备忘录模式(Memento Pat ...
- Java 设计模式系列(二十)状态模式
Java 设计模式系列(二十)状态模式 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式.状态模式允许一个对象在其内部状态改变的时候改 ...
- 桥接模式 桥梁模式 bridge 结构型 设计模式(十二)
桥接模式Bridge Bridge 意为桥梁,桥接模式的作用就像桥梁一样,用于把两件事物连接起来 意图 将抽象部分与他的实现部分进行分离,使得他们都可以独立的发展. 意图解析 依赖倒置原 ...
- Java 设计模式系列(二二)责任链模式
Java 设计模式系列(二二)责任链模式 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链.请求在这个链上传递,直到链上的某一个对象决定处理此请求 ...
- 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条
http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...
- Java进阶(三十二) HttpClient使用详解
Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...
- Java进阶(五十二)利用LOG4J生成服务日志
Java进阶(五十二)利用LOG4J生成服务日志 前言 由于论文写作需求,需要进行流程挖掘.前提是需要有真实的事件日志数据.真实的事件日志数据可以用来发现.监控和提升业务流程. 为了获得真实的事件日志 ...
- Java设计模式(20)观察者模式(Observer模式)
Java深入到一定程度,就不可避免的碰到设计模式(design pattern)这一概念,了解设计模式,将使自己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广泛,遵循 ...
- “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
随机推荐
- centos7谷歌chrome内网部署演示
上传需要的包,注释网关创建内网环境 [root@localhost ~]# ls anaconda-ks.cfg chrome mcw4 mcw4.tar.gz mcwchromerpm.tar.gz ...
- Perl操作excel2007的模块
详细版:https://www.jianshu.com/p/84bda53827c8 第一种方法: 读写excel2007文档的perl模块: Spreadsheet::XLSX(读)和Spreads ...
- 网页常用的css特效让互动留住客户
一般网站如果制作按钮,多做一些互动,可以让客户获得更好的体验. 例如鼠标滑过按钮,让背景颜色从左往右滑出来(或者从右往左都可以): <a target="_blank" hr ...
- WPF中的命令(Command)
这节来讲一下WPF中的命令(Command)的使用. [认识Command] 我们之前说过,WPF本身就为我们提供了一个基础的MVVM框架,本节要讲的命令就是其中一环,通过在ViewModel中声明命 ...
- 写了10000条Airtest截图脚本总结出来的截图经验,赶紧收藏!
前言 今天想先给大家分享1个小白用户的Airtest从入门到放弃的故事: 小A是一个自动化的小白,在逛测试论坛的时候,偶然间发现了Airtest这个基于图像识别的UI自动化框架. 出于好奇,小A试用了 ...
- [对对子队]会议记录4.19(Scrum Meeting10)
今天已完成的工作 何瑞 工作内容:搭建第2关,基本完成第3关 相关issue:搭建关卡2.3 相关签入:4.19签入1 4.19签入2 刘子航 工作内容:完成关卡选择界面的设计图 ...
- [技术博客] 软工-Ruby on Rails 后端开发总结分享
[技术博客] 软工-Ruby on Rails 后端开发总结分享 在这次软件编写中,我们的后端使用了Ruby on Rails (RoR)框架. Rails框架是用Ruby编写的.这意味着当我们为Ru ...
- [技术博客] Django中文件的保存与访问
[技术博客] Django中文件的保存与访问 在TextMarking项目开发中,数据库需要保存用户上传的文本文档. 原型设计:用户点击上传文本->保存文本->文本发送到后端保存为文件. ...
- 算法:Z字型(Zigzag)编排
问题:给定 n 行和 m 列的二维数组矩阵.如图所示,以 ZIG-ZAG 方式打印此矩阵. 从对称的角度来看,通过反复施加滑行反射可以从简单的图案如线段产生规则的之字形. 主要思想:算法从(0, 0) ...
- 转载: XILINX GT的基本概念
https://zhuanlan.zhihu.com/p/46052855 本来写了一篇关于高速收发器的初步调试方案的介绍,给出一些遇到问题时初步的调试建议.但是发现其中涉及到很多概念.逐一解释会导致 ...