观察者模式(Observer Design Pattern),也叫做发布订阅模式(Publish-Subscribe Design Pattern)、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式。指在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

比如说Redis 中的基于频道的发布订阅就是观察者模式的应用:

一、观察者模式的介绍

观察者模式是一种对象行为型模式,下面就来看看观察者模式的结构及其实现:

1.1 观察者模式的结构

观察者模式结构中主要包括观察目标(Object)和观察者(Observer)主要结构:

Subject
ConcreteSubject
Observer
ConcreteObserver1、ConcreteObserver2
Client

1.2 观察者模式的实现

根据上面的类图,我们可以实现对应的代码。

首先定义一个抽象目标类 Subject ,其中包括增加、注销和通知观察者方法

public abstract class Subject {protected List<Observer> observerList = new ArrayList<Observer>();/*** 增加观察者* @param observer 观察者*/public void add(Observer observer) {observerList.add(observer);}/*** 注销观察者,从观察者集合中删除一个观察者* @param observer 观察者*/public void remove(Observer observer) {observerList.remove(observer);}/**通知观察者*/public abstract void notifyObserver();
}

对应具体的目标类 ConcreteSubject

public class ConcreteSubject extends Subject{@Overridepublic void notifyObserver() {System.out.println("遍历观察者:");for (Observer observer : observerList) {observer.response();}}
}

此外需要定义抽象观察者 Observer ,它一般定义为一个接口,声明一个 response() 方法,为不同观察者的相应行为定义相同的接口:

public interface Observer {/**声明响应方法*/void response();
}

具体的观察者实现:

public class ConcreteObserver1 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver1");}
}public class ConcreteObserver2 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver2");}
}

最后是客户端测试:

public class Client {public static void main(String[] args) {Subject concreteSubject = new ConcreteSubject();//具体观察者Observer concreteObserver1 = new ConcreteObserver1();Observer concreteObserver2 = new ConcreteObserver2();concreteSubject.add(concreteObserver1);concreteSubject.add(concreteObserver2);concreteSubject.notifyObserver();}
}

测试结果:

遍历观察者:
我是具体观察者ConcreteObserver1
我是具体观察者ConcreteObserver2

二、观察者模式的应用场景

在以下情况就可以考虑使用观察者模式:

  1. 一个对象的改变会导致一个或多个对象发生改变,而并不知道具体有多少对象将会发生改变,也不知道这些对象是谁
  2. 当一个抽象模型有两个方面,其中的一个方面依赖于另一个方面时,可将这两者封装在独立的对象中以使他们可以各自独立地改变和复用
  3. 需要在系统中创建一个触发链,使得事件拥有跨域通知(跨越两种观察者的类型)

2.1 观察者模式在java.util包中的应用

观察者模式在JDK中就有典型应用,比如 java.util.Observable 和 java.util.Observer 类。结构如下图所示:

我们可以通过实现具体的 ConcreteObserver 和具体的 ConcreteObservable 完成观察者模式流程

2.2 观察者模式在MVC中的应用

MVC(Modew-View-Controller)架构中也应用了观察者模式,其中模型(Model)可以对应观察者模式中的观察目标,而视图(View)对应于观察者,控制器(Controller)就是中介者模式的应用:

三、观察者模式实战

在本案例中模拟北京小客车指标摇号事件的通知场景(来源于《重学Java设计模式》)

对于通知事件,可以将其分成三个部分: 事件监听 、 事件处理 和 具体的业务流程 ,如下图所示:

对于和核心流程和非核心流程的结构,非核心流程可以是异步的,在MQ以及定时任务的处理下,能够最终保证一致性。

具体代码实现

  1. 事件监听接口及具体实现

这个部分就相当于观察者(Observer)的角色

在接口中定义基本事件类方法 doEvent()

public interface EventListener {void doEvent(LotteryResult result);}

监听事件的具体实现 MessageEventListener (短消息事件)和 MQEventListener (MQ发送事件)

public class MessageEventListener implements EventListener{private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);@Overridepublic void doEvent(LotteryResult result) {logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg());}
}public class MQEventListener implements EventListener{private Logger logger = LoggerFactory.getLogger(MQEventListener.class);@Overridepublic void doEvent(LotteryResult result) {logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg());}
}
  1. 事件处理类

该部分就相当于主题(Object)部分

对于不同的事件类型(MQ和Message)进行枚举处理,并提供三个方法: subscribe() 、 unsubscribe() 和 notify() 用于对监听事件的注册和使用:

public class EventManager {Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {listeners.put(operation, new ArrayList<>());}}public enum EventType {MQ,Message}/*** 订阅* @param eventType 事件类型* @param listener  监听*/public void subscribe(Enum<EventType> eventType, EventListener listener) {List<EventListener> eventListeners = listeners.get(eventType);eventListeners.add(listener);}/*** 取消订阅* @param eventType 事件类型* @param listener 监听*/public void unsubscribe(Enum<EventType> eventType, EventListener listener) {List<EventListener> eventListeners = listeners.get(eventType);eventListeners.remove(listener);}/*** 通知* @param eventType 事件类型* @param result    结果*/public void notify(Enum<EventType> eventType, LotteryResult result) {List<EventListener> eventListeners = listeners.get(eventType);for (EventListener eventListener : eventListeners) {eventListener.doEvent(result);}}
}
  1. 业务抽象类接口及其实现

使用抽象类的方式实现方法,好处是可以在方法中扩展额外的调用,并提供抽象方法 doDraw ,让继承者去实现具体的逻辑

public abstract class LotteryService {private EventManager eventManager;public LotteryService() {eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());}public LotteryResult draw(String uId) {LotteryResult lotteryResult = doDraw(uId);eventManager.notify(EventManager.EventType.MQ, lotteryResult);eventManager.notify(EventManager.EventType.Message, lotteryResult);return lotteryResult;}protected abstract LotteryResult doDraw(String uId);
}public class LotteryServiceImpl extends LotteryService{private MinibusTargetService minibusTargetService = new MinibusTargetService();@Overrideprotected LotteryResult doDraw(String uId) {//摇号测试String lottery = minibusTargetService.lottery(uId);return new LotteryResult(uId, lottery, new Date());}
}
  1. 其他的类

摇号服务接口:

/*** 小客车指标调控服务*/
public class MinibusTargetService {/*** 模拟摇号,但不是摇号算法** @param uId 用户编号* @return 结果*/public String lottery(String uId) {return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码".concat(uId).concat("在本次摇号中签") : "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期");}}

事件信息返回类:

public class LotteryResult {private String uId;private String msg;private Date dateTime;//get set constructor...
}
  1. 测试类
public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);@Testpublic void test() {LotteryServiceImpl lotteryService = new LotteryServiceImpl();LotteryResult result = lotteryService.draw("1234567");logger.info("摇号结果:{}", JSON.toJSONString(result));}
}

测试结果:

11:43:09.284 [main] INFO  c.e.d.event.listener.MQEventListener - 记录用户 1234567 摇号结果(MQ):恭喜你,编码1234567在本次摇号中签
11:43:09.288 [main] INFO  c.e.d.e.l.MessageEventListener - 给用户 1234567 发送短信通知(短信):恭喜你,编码1234567在本次摇号中签
11:43:09.431 [main] INFO  ApiTest - 摇号结果:{"dateTime":1649475789279,"msg":"恭喜你,编码123

想要了解更多,可以私信我也可以在评论区告诉我。

设计模式学习笔记:观察者模式及应用场景相关推荐

  1. 设计模式学习笔记-观察者模式(转)

    http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html 1. 概述 有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系, ...

  2. 设计模式学习笔记-观察者模式

    1. 概述 有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 2. ...

  3. Java-马士兵设计模式学习笔记-观察者模式-读取properties文件改成单例模式

    一.概述 1.目标:读取properties文件改成单例模式 二.代码 1.Test.java 1 class WakenUpEvent{ 2 3 private long time; 4 priva ...

  4. 7 种 Javascript 常用设计模式学习笔记

    7 种 Javascript 常用设计模式学习笔记 由于 JS 或者前端的场景限制,并不是 23 种设计模式都常用. 有的是没有使用场景,有的模式使用场景非常少,所以只是列举 7 个常见的模式 本文的 ...

  5. 36设计模式学习笔记

    设计模式学习笔记 文章目录 0.设计模式简介 1.单例模式 2.工厂方法 3.抽象工厂 4.责任链模式 5.模板方法 ok 一.模板方法简介 二.模式的定义和特点 三.模式的结构和实现 四.什么是模板 ...

  6. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  7. 设计模式学习笔记清单

    设计模式学习笔记清单 关于设计模式许多人已经耳熟能详,这段时间结合李建忠的教学视频以及大量网络资料,把这部分过了一遍,整理出学习笔记,而真正的深入学习和理解只能在具体的开发环境中日积月累.      ...

  8. 步步为营 .NET 设计模式学习笔记系列总结

    设计模式我从开篇到23种设计模式的讲解总共花了进两个月的时间,其间有很多读者给我提出了很好的建议,同时也指出了我的不足,对此我表示感谢,正是由于很多读者的支持我才能坚持的写到最后.在此表示我真诚的谢意 ...

  9. 设计模式学习笔记(十七)——Command命令模式

    设计模式学习笔记(十七)--Command命令模式 Command命令模式介绍: Command命令模式是一种对象行为型模式,它主要解决的问题是:在软件构建过程中,"行为请求者"与 ...

  10. 设计模式学习笔记——观察者(Observer)模式

    设计模式学习笔记--观察者(Observer)模式 @(设计模式)[设计模式, 观察者模式, Observer] 设计模式学习笔记观察者Observer模式 基本介绍 观察者案例 类图 实现代码 Ob ...

最新文章

  1. css盒子模型、文档流、相对与绝对定位、浮动与清除模型
  2. 16.Hadoop架构再探讨第2部分
  3. Performance Prism
  4. 数据结构之---二叉树C实现
  5. java基础——枚举
  6. java public object_Java_Object
  7. LeetCode 1952. 三除数
  8. java中包容易出现的错误及权限问题
  9. mysql fn_2019 数据库趋势报告,最受欢迎的是 MySQL
  10. 列表的下标;查找,判断,增加,删除函数
  11. 【计算机组成原理】第1章 计算机系统概论
  12. js页面重定向跳转代码总结(待续)
  13. 无语!JDK 8 中的 HashMap 依然会死循环…
  14. 智能制造与数字化工厂
  15. http抓包实践--(二)--web网页抓包和fiddler修改包
  16. 网络安全实验室 脚本关 解析
  17. PHP实现生成二维码的示例代码
  18. android一维码生成,android 使用Zxing生成条形码(一维)
  19. 地址解析:使用Google API将地址文本转换为经纬度
  20. 计算机里的文件夹可以加密吗,如何加密电脑中的文件夹

热门文章

  1. PSOS通信,同步,互斥
  2. 十三、城市建成区时空扩张分析——标准差椭圆
  3. 几个主流TCP/IP协议栈介绍
  4. Android 通过阿里语音实现打电话功能
  5. 从零开始强化学习一:Bandit Problem
  6. Hive一条SQL使用随机值创建测试表一百万条数据
  7. POLYCOM的设置方法(转载)
  8. 8000字总结的前端性能优化
  9. IEC101平衡链路通信恢复
  10. 盘点中国式家教的三大悲哀