第十五章 观察者模式

2024-06-12 12:28

本文主要是介绍第十五章 观察者模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1 观察者模式介绍

2 观察者模式原理

3 观察者模式实现

4 观察者模式应用实例

 5 观察者模式总结


1 观察者模式介绍

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.

现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

2 观察者模式原理

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致.

3 观察者模式实现

  • 观察者
/*** 抽象观察者* @author spikeCong* @date 2022/10/11**/
public interface Observer {//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现public void update();
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverOne implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverOne 得到通知!");}
}/*** 具体观察者* @author spikeCong* @date 2022/10/11**/
public class ConcreteObserverTwo implements Observer {@Overridepublic void update() {//获取消息通知,执行业务代码System.out.println("ConcreteObserverTwo 得到通知!");}
}
  • 被观察者
/*** 抽象目标类* @author spikeCong* @date 2022/10/11**/
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}/*** 具体目标类* @author spikeCong* @date 2022/10/11**/
public class ConcreteSubject implements Subject {//定义集合,存储所有观察者对象private ArrayList<Observer> observers = new ArrayList<>();//注册方法,向观察者集合中增加一个观察者@Overridepublic void attach(Observer observer) {observers.add(observer);}//注销方法,用于从观察者集合中删除一个观察者@Overridepublic void detach(Observer observer) {observers.remove(observer);}//通知方法@Overridepublic void notifyObservers() {//遍历观察者集合,调用每一个观察者的响应方法for (Observer obs : observers) {obs.update();}}
}
  • 测试类
public class Client {public static void main(String[] args) {//创建目标类(被观察者)ConcreteSubject subject = new ConcreteSubject();//注册观察者类,可以注册多个subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo());//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。subject.notifyObservers();}
}

4 观察者模式应用实例

1 ) 未使用设计模式

/*** 模拟买房摇号服务* @author spikeCong* @date 2022/10/11**/
public class DrawHouseService {//摇号抽签public String lots(String uId){if(uId.hashCode() % 2 == 0){return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";}else{return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";}}
}public class LotteryResult {private String uId; // 用户idprivate String msg; // 摇号信息private Date dataTime; // 业务时间//get&set.....
}/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public interface LotteryService {//摇号相关业务public LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl implements LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);//发短信System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);//发送MQ消息System.out.println("记录用户摇号结果(MQ), 用户ID:" +  uId + ",摇号结果:" + result);return new LotteryResult(uId,result,new Date());}
}@Test
public void test1(){LotteryService ls = new LotteryServiceImpl();String result  = ls.lottery("1234567887654322");System.out.println(result);
}

1 ) 使用观察者模式进行优化

  • 事件监听
/*** 事件监听接口* @author spikeCong* @date 2022/10/11**/
public interface EventListener {void doEvent(LotteryResult result);
}/*** 短信发送事件* @author spikeCong* @date 2022/10/11**/
public class MessageEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("发送短信通知用户ID为: " + result.getuId() +",您的摇号结果如下: " + result.getMsg());}
}/*** MQ消息发送事件* @author spikeCong* @date 2022/10/11**/
public class MQEventListener implements EventListener {@Overridepublic void doEvent(LotteryResult result) {System.out.println("记录用户摇号结果(MQ), 用户ID:" +  result.getuId() +",摇号结果:" + result.getMsg());}
}

 事件处理

/*** 事件处理类* @author spikeCong* @date 2022/10/11**/
public class EventManager {public enum EventType{MQ,Message}//监听器集合Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();public EventManager(Enum<EventType>... operations) {for (Enum<EventType> operation : operations) {this.listeners.put(operation,new ArrayList<>());}}/*** 订阅* @param eventType 事件类型* @param listener  监听*/public void subscribe(Enum<EventType> eventType, EventListener listener){List<EventListener> users = listeners.get(eventType);users.add(listener);}/*** 取消订阅* @param eventType 事件类型* @param listener  监听*/public void unsubscribe(Enum<EventType> eventType,EventListener listener){List<EventListener> users = listeners.get(eventType);users.remove(listener);}/*** 通知* @param eventType 事件类型* @param result    结果*/public void notify(Enum<EventType> eventType, LotteryResult result){List<EventListener> users = listeners.get(eventType);for (EventListener listener : users) {listener.doEvent(result);}}

摇号业务处理

/*** 开奖服务接口* @author spikeCong* @date 2022/10/11**/
public abstract class LotteryService{private EventManager eventManager;public LotteryService(){//设置事件类型eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);//订阅eventManager.subscribe(EventManager.EventType.Message,new MessageEventListener());eventManager.subscribe(EventManager.EventType.MQ,new MQEventListener());}public LotteryResult lotteryAndMsg(String uId){LotteryResult result = lottery(uId);//发送通知eventManager.notify(EventManager.EventType.Message,result);eventManager.notify(EventManager.EventType.MQ,result);return result;}public abstract LotteryResult lottery(String uId);
}/*** 开奖服务* @author spikeCong* @date 2022/10/11**/
public class LotteryServiceImpl extends LotteryService {//注入摇号服务private DrawHouseService houseService = new DrawHouseService();@Overridepublic LotteryResult lottery(String uId) {//摇号String result = houseService.lots(uId);return new LotteryResult(uId,result,new Date());}
}

测试

@Test
public void test2(){LotteryService ls = new LotteryServiceImpl();LotteryResult result  = ls.lotteryAndMsg("1234567887654322");System.out.println(result);
}

 5 观察者模式总结

1) 观察者模式的优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

2) 观察者模式的缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

3 ) 观察者模式常见的使用场景

  • 当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
  • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
  • 需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
  • 微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
  • 需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

4 ) JDK 中对观察者模式的支持

JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.

  • java.util.Observer 接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法.

    void update(Observable o, Object arg);
    
  • java.util.Observable 类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。

    • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
    • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。

    • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

这篇关于第十五章 观察者模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1054216

相关文章

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

SQL Server身份验证模式步骤和示例代码

《SQLServer身份验证模式步骤和示例代码》SQLServer是一个广泛使用的关系数据库管理系统,通常使用两种身份验证模式:Windows身份验证和SQLServer身份验证,本文将详细介绍身份... 目录身份验证方式的概念更改身份验证方式的步骤方法一:使用SQL Server Management S

Redis高可用-主从复制、哨兵模式与集群模式详解

《Redis高可用-主从复制、哨兵模式与集群模式详解》:本文主要介绍Redis高可用-主从复制、哨兵模式与集群模式的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录Redis高可用-主从复制、哨兵模式与集群模式概要一、主从复制(Master-Slave Repli

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M