【观察者】设计模式:构建灵活且响应式的软件系统

news2024/11/15 17:19:38

引言

在软件开发中,我们经常面临需要在多个对象之间进行通信的挑战。特别是当一个对象的状态发生变化时,我们希望所有依赖于这个状态的对象都能自动更新。这就是观察者设计模式大显身手的地方。

简介

观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,使得当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

核心组件

  • Subject(主题):也称为Observable,它维护一组观察者,提供添加、删除和通知观察者的接口;
  • Observer(观察者):为所有具体观察者定义一个接口,在得到主题的通知时更新自己;
  • ConcreteSubject(具体主题):保存状态,当状态变化时通知观察者;
  • ConcreteObserver(具体观察者):实现观察者更新接口,以便在主题状态变化时更新自己。

经典实现

// 主题-被观察者
public interface Subject {
  void registerObserver(Observer observer); //注册观察者
  void removeObserver(Observer observer); //移除观察者
  void notifyObservers(Message message); //通知观察者
}
// 观察者
public interface Observer {
  void update(Message message);
}

public class ConcreteSubject implements Subject {
  private List<Observer> observers = new ArrayList<Observer>();

  @Override
  public void registerObserver(Observer observer) {
    observers.add(observer);
  }

  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }

  @Override
  public void notifyObservers(Message message) {
    for (Observer observer : observers) {
      observer.update(message);
    }
  }
}

//观察者1
public class ConcreteObserverOne implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverOne is notified.");
  }
}
//观察者2
public class ConcreteObserverTwo implements Observer {
  @Override
  public void update(Message message) {
    //TODO: 获取消息通知,执行自己的逻辑...
    System.out.println("ConcreteObserverTwo is notified.");
  }
}

public class Demo {
  public static void main(String[] args) {
    ConcreteSubject subject = new ConcreteSubject();
    subject.registerObserver(new ConcreteObserverOne());
    subject.registerObserver(new ConcreteObserverTwo());
    subject.notifyObservers(new Message());
  }
}

投资理财系统

比如,我们现在在开发一个投资理财系统,用户注册成功后,会给用户发放投资体验金,代码实现大致如下:

public class UserController {
  private UserService userService; // 依赖注入
  private PromotionService promotionService; // 依赖注入

  public Long register(String telephone, String password) {
    // 注册
    long userId = userService.register(telephone, password);
    // 发放体验金
    promotionService.issueNewUserExperienceCash(userId);
    return userId;
  }
}

假如没有扩展修改的需求,那么现在的代码是可以被接受的。如果非得用观察者模式,就会引入更多的类和更加复杂的代码结构,反而是一种过度设计

相反,假如需求频繁改动,比如用户注册成功后,不再发放体验金,而是改为发放优惠券,且给用户发送一封“注册成功”的站内信。此时,就需要频繁修改register函数的代码,违反开闭原则。
而且,如果注册成功后需要执行的后续操作越来越多,那register函数也会越来越复杂,影响代码的可读性和可维护性。

同步阻塞实现

//观察者接口
public interface RegObserver {
  void handleRegSuccess(long userId);
}

//观察者1
public class RegPromotionObserver implements RegObserver {
  private PromotionService promotionService; // 依赖注入

  @Override
  public void handleRegSuccess(long userId) {
    promotionService.issueNewUserExperienceCash(userId);
  }
}
//观察者2
public class RegNotificationObserver implements RegObserver {
  private NotificationService notificationService;

  @Override
  public void handleRegSuccess(long userId) {
    notificationService.sendInboxMessage(userId, "Welcome...");
  }
}

public class UserController {
  private UserService userService; // 依赖注入
  private List<RegObserver> regObservers = new ArrayList<>();

  // 设置观察者
  public void setRegObservers(List<RegObserver> observers) {
    regObservers.addAll(observers);
  }

  public Long register(String telephone, String password) {
    long userId = userService.register(telephone, password);
    //通知观察者
    for (RegObserver observer : regObservers) {
      observer.handleRegSuccess(userId);
    }
    return userId;
  }
}

被观察者代码和观察者代码在同一个线程中执行,被观察者代码一直阻塞,直到所有的观察者代码都执行完毕后,才执行后续的代码。
register函数依次调用执行每个观察者的handleRegSuccess函数,都执行完成后,才会返回结果给客户端。

异步非阻塞实现

如果注册接口是一个调用非常频繁的接口,对性能非常敏感,希望接口的响应时间尽可能的短,可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
register函数执行完成后,启动一个新的线程来执行观察者的handleRegSuccess函数。

跨进程实现

同步阻塞和异步非阻塞都是进程内的实现方式。
而基于消息队列实现的方式则属于一个跨进程的实现方式,观察者和被观察者解耦的更加彻底,两者都感知不到对方的存在。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。
在这里插入图片描述

总结

观察者模式提供了一种强大的方法来实现对象之间的松耦合通信。它允许系统在不修改现有代码的情况下,通过增加新的观察者来扩展其功能。通过使用观察者模式,我们可以构建出更加灵活、可维护和响应式的软件系统。

参考文献
《极客时间-设计模式之美》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2113695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习】XGBoost的用法和参数解释

一、XGBoost的用法 流程&#xff1a; 代码案例&#xff1a; 二、XGBoost的几大参数 1、一般参数&#xff0c;用于集成算法本身 ①n_estimators 集成算法通过在数据上构建多个弱 评估器&#xff0c;汇总所有弱评估器的建模结果&#xff0c;以获取比单个模型更好的回归或分类…

C语言蓝桥杯

一、语言基础 竞赛常用库函数 最值查询 min_element和max_element在vector(迭代器的使用) nth_element函数的使用 例题lanqiao OJ 497成绩分析 第一种用min_element和max_element函数的写法 第二种用min和max的写法 二分查找 二分查找只能对数组操作 binary_search函数&…

【ZYNQ】OV5640 介绍

OV5640 是 OmniVison (豪威) 公司推出的高性能数字图像传感器,支持最高 500 万像素图像采集,支持 RAW RGB、RGB565、YCbCr422 等图像格式输出。同时 OV5640 可用于图像补偿,包括自动曝光、白平衡、伽马调节、饱和度调节等功能。本文主要介绍 OV5640 工作原理与接口定义。 目…

xss-labs-master通关教程

一.level1 先来进行一下代码审计 <?php ini_set("display_errors", 0);//关闭错误显示 $str $_GET["name"]; //接受URL来的get形式的name传参 echo "<h2 aligncenter>欢迎用户".$str."</h2>";//在网页输出&#x…

HTB-You know 0xDiablos

引言 项目概述&#xff1a;HTB的EASY难度 PWN靶机 You know 0xDiablos https://app.hackthebox.com/challenges/106 本文详细解释了溢出payload的结构以及为什么要这样构造&#xff0c;友好的为想要入手PWN的朋友解释了原理技术点涉及: 32位linux逆向、32位程序调用、栈溢出目…

八叉树CSG布尔运算

1. 前言 布尔运算是几何内核的核心话题之一&#xff0c;又分为二维布尔运算和三维布尔运算&#xff0c;其中三维布尔运算技术难度较高&#xff0c;缘之空间维度的提升。 根据实现的理论不同&#xff0c;三维布尔运算又分为B-Rep布尔运算、CSG布尔运算、三角网格布尔运算等方法…

MySQL5.6.51安装

1 官网下载 MySQL :: Download MySQL Community Server (Archived Versions) 2 解压 3 配置环境 D:\MySQL\mysql-5.6.51-winx64\bin 把这个配置到path后面 4 启动 5 设置初始密码 set password for rootlocalhost password(123456); 6 登录mysql

【数据结构】栈与队列OJ题(用队列实现栈)(用栈实现队列)

目录 1.用队列实现栈oj题 对比 一、初始化 二、出栈 三、入栈 四、取队头元素&#xff1a; 2.用栈实现队列 一、定义 二、入队列 三、出队列 四、队头 五、判空 前言&#xff1a;如果想了解什么是栈和队列请参考上一篇文章进来一起把【数据结构】的【栈与队列】狠…

Cursor是什么?Cursor Pro Plus 如何订阅升级教程

一、Cursor是什么&#xff1f; Cursor 是一个基于 Visual Studio Code&#xff08;VS Code&#xff09;技术构建的高级代码编辑器&#xff0c;专为提高编程效率并更深度地整合 AI 功能而设计。它不仅继承了 VS Code 的强大功能和用户界面&#xff0c;还增加了专门针对 AI 支持…

Kafka3.6.0 linux 安装,非zk模式

1.下载 到kafka官网下载 2.上传到服务器指定位置 3.解压到指定目录&#xff0c;这里我放到/opt下 tar -xzvf kafka_2.13-3.6.0.tgz -C /opt/ 4.修改配置 因为我们不使用zookeeper&#xff0c;所以来到kafka装根目录的下的/config/kraft/ cd /opt/kafka_2.13-3.6.0/config…

前端HTML基础笔记

HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是一种用于创建网页的标准标记语言。它通过一系列的元素&#xff08;或称为标签&#xff09;来定义网页的结构和内容。HTML文档由一系列的元素组成&#xff0c;这些元素可以包含文本、图片、链…

伽罗华域GF的简单计算

伽罗华域&#xff08;Galois Field&#xff09;&#xff0c;也称为有限域&#xff0c;是一个包含有限个元素的代数结构&#xff0c;满足加法、减法、乘法和除法&#xff08;除以零除外&#xff09;运算。伽罗华域在编码理论、密码学、数字信号处理等领域有广泛的应用。它以法国…

思科网络地址转换5

#网络安全技术实现# #任务五利用动态NAPT实现局域网访问Internet5# #1配置计算机的IP 地址、子网掩码和网关 #2配置路由器A的主机名称及其接口IP地址 Router>enable Router#conf t Router(config)#hostname Router-A Router-A(config)#int f0/0 Router-A(config-if)#i…

【Python报错已解决】 AttributeError: ‘move_to‘ requires a WebElement

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;检查元素选择器2.2 方法…

计算机毕业设计选题-基于python的企业人事管理系统【源码+文档+数据库】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的企业人事管理系…

mycat双主高可用架构部署-MySQL5.7环境部署第一台

MySQL5.7服务器IP是192.168.31.209及192.168.31.210 1、192.168.31.209:3307实例部署 a、配置文件 mkdir -p /data/mysql/mysql3307/{data,logs} #创建MySQL数据及日志目录 vi /data/mysql/mysql3307/my3307.cnf #配置文件整理 [client] #password your_password port …

【LVI-SAM】激光雷达点云处理特征提取LIO-SAM 之FeatureExtraction实现细节

激光雷达点云处理特征提取LIO-SAM 之FeatureExtraction实现细节 1. 特征提取实现过程总结1.0 特征提取过程小结1.1 类 FeatureExtraction 的整体结构与作用1.2 详细特征提取的过程1. 平滑度计算&#xff08;calculateSmoothness()&#xff09;2. 标记遮挡点&#xff08;markOcc…

堆-数组的堆化+优先队列(PriorityQueue)的使用

一、堆 1、什么是堆&#xff1f; 以完全二叉树的形式将元素存储到对应的数组位置上所形成的新数组 2、为什么要将数组变成堆&#xff1f; 当数组中的元素连续多次进行排序时会消耗大量的时间&#xff0c;将数组变成堆后通过堆排序的方式将会消耗更少的时间 二、接口 给堆…

python | 字符串字母大小写转换方法

在对字符串所含字母单词进行处理时&#xff0c;经常会对其格式进行转换统一。 python自带了一些判断和处理转换的方法。 一、字符串格式判断方法 islower()&#xff1a;str.islower()&#xff0c;判断字符串是否全是小写&#xff0c;是返回True&#xff0c;不是返回False i…

Transiting from CUDA to HIP(三)

一、Workarounds 1. memcpyToSymbol 在 HIP (Heterogeneous-compute Interface for Portability) 中&#xff0c;hipMemcpyToSymbol 函数用于将数据从主机内存复制到设备上的全局内存或常量内存中&#xff0c;这样可以在设备端的内核中访问这些数据。这个功能特别有用&#x…