Java 设计模式——观察者模式

news2025/2/1 16:54:46

目录

  • 1.概述
  • 2.结构
  • 3.案例实现
    • 3.1.抽象观察者
    • 3.2.观察对象
    • 3.3.具体观察者
    • 3.4.具体观察对象
    • 3.5.测试
  • 4.优缺点
  • 5.使用场景
  • 6.JDK 源码解析——Observable / Observer
    • 6.1.Observable 类
    • 6.2.Observer 接口
    • 6.3.案例

1.概述

观察者模式 (Observer Pattern) 是一种行为型设计模式,又被称为发布-订阅 (Publish/Subscribe) 模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会自动收到通知并更新

2.结构

在观察者模式中有如下角色:

  • Subject:观察对象,定义了注册观察者和删除观察者的方法。此外,它还声明了“获取现在的状态”的方法。
  • ConcreteSubject:具体观察对象,当自身状态发生变化后,它会通知所有已经注册的 Observer 角色。
  • Observer:抽象观察者,负责接收来自 Subject 角色的状态变化的通知,为此,它声明了 update 方法。
  • ConcrereObserver:具体观察者,当它的 update 方法被调用后,会去获取要观察的对象的最新状态。

3.案例实现

【例】微信公众号。在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是观察对象,有多个的微信用户关注了程序猿这个公众号。 具体类图如下:
在这里插入图片描述
具体实现代码如下:

3.1.抽象观察者

Observer.java

public interface Observer {
    void update(String message);
}

3.2.观察对象

Subject.java

//观察对象
public interface Subject {
    
    //添加订阅者(观察者对象)
    void attach(Observer observer);
    
    //删除订阅者
    void detach(Observer observer);
    
    //通知订阅者更新消息
    void notify(String message);
}

3.3.具体观察者

WeiXinObserver.java

//具体的观察者角色类
public class WeiXinObserver implements Observer{
    
    private String name;
    
    public WeiXinObserver(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String message) {
        System.out.println(name + " : " + message);
    }
}

3.4.具体观察对象

SubscriptionSubject.java

import java.util.ArrayList;
import java.util.List;

//具体主题角色类
public class SubscriptionSubject implements Subject{
    
    //定义一个集合,用来存储多个观察者对象
    private List<Observer> weiXinUserList = new ArrayList<>();
    
    @Override
    public void attach(Observer observer) {
        weiXinUserList.add(observer);
    }
    
    @Override
    public void detach(Observer observer) {
        weiXinUserList.remove(observer);
    }
    
    @Override
    public void notify(String message) {
        //遍历集合
        for (Observer observer : weiXinUserList) {
            //调用观察者对象中的 update 方法
            observer.update(message);
        }
    }
}

3.5.测试

Client.java

public class Client {
    public static void main(String[] args) {
        //1.创建公众号对象
        SubscriptionSubject subject = new SubscriptionSubject();
        
        //2.订阅公众号
        subject.attach(new WeiXinObserver("Tom"));
        subject.attach(new WeiXinObserver("Mike"));
        subject.attach(new WeiXinObserver("Jerry"));
        
        //3.公众号更新
        subject.notify("Java专栏更新了!");
    }
}

4.优缺点

(1)优点

  • 解耦性:观察者模式将观察者和主题(被观察者)解耦,使得它们之间的依赖关系变得松散。主题只需要知道观察者的抽象接口,而不需要知道具体的观察者对象。
  • 可扩展性:通过新增或删除观察者,可以方便地扩展系统的功能。主题可以随时添加或删除观察者,而不会影响到其他部分的代码。
  • 广播通信:主题通知所有的观察者对象,实现事件的广播通信,方便多个对象对事件做出响应。
  • 符合开闭原则:新增观察者时无需修改主题的代码,只需实现新的观察者接口即可,符合开闭原则的要求。

(2)缺点

  • 观察者过多可能导致性能问题:如果观察者过多,或者通知频繁,可能会导致系统性能下降。因此在设计时需注意对观察者的控制。
  • 观察者和主题关系不明确:观察者模式中的观察者和主题之间的关系是松散的,观察者无法准确知道有哪些其他观察者存在。可能存在一些潜在的问题和维护的困难。

5.使用场景

(1)观察者模式适用于以下场景:

  • 发布-订阅模型:当存在一个主题(或发布者)需要通知多个观察者(或订阅者)并进行相应的处理时,可以使用观察者模式。例如,新闻发布机构将新闻发布给多个订阅者。
  • UI 事件处理:在图形用户界面 (GUI) 开发中,观察者模式常用于处理用户界面上的事件。用户操作(如按钮点击、文本框输入等)可以作为主题,而界面元素的更新则可以作为观察者来处理。
  • 实时数据更新:当需要将实时数据的更新通知给多个组件或模块时,可以使用观察者模式。例如,股票交易系统中,股票价格的变化可以作为主题,而界面上的多个组件可以作为观察者。
  • 消息队列系统:在消息队列系统中,观察者模式可以用于消息的发布和订阅的机制。生产者发布消息到主题中,而消费者作为观察者订阅主题,在消息到达时进行相应的处理。
  • MVC 架构:观察者模式常用于应用程序的 MVC(模型-视图-控制器)架构中,用于实现模型与视图之间的通信。当模型的状态发生变化时,观察者模式可以通知相关的视图来刷新界面。
  • 多线程编程中的消息通知:在多线程编程中,观察者模式可以用于实现线程间的消息通知机制。一个线程可以作为主题,而其他线程可以作为观察者,通过观察者模式实现线程间的协同工作。

(2)总之,观察者模式适用于需要一对多的依赖关系,并且对观察者和被观察者之间的耦合度要求较低的场景。它常用于发布-订阅模型、UI 事件处理、实时数据更新、消息队列系统、MVC 架构等应用场景。

6.JDK 源码解析——Observable / Observer

在 Java 中,通过 java.util.Observable 类java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

6.1.Observable 类

其源码如下:

package java.util;

public class Observable {
	/*
		changed 为一个 boolean 类型的内部标志,注明目标对象是否发生了变化
		当它为 true 时,notifyObservers() 才会通知观察者
	*/
    private boolean changed = false;
    //存储所有的观察者对象
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

	//用于将新的观察者对象添加到集合中
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    //从集合中删除一个观察者
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {      
        Object[] arrLocal;

        synchronized (this) {         
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
		
        for (int i = arrLocal.length-1; i>=0; i--)
        	//逆序通知集合中的观察者更新消息
            ((Observer)arrLocal[i]).update(this, arg);
    }

    //删除所集合中所有的观察者
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    //返回集合中观察者的数量
    public synchronized int countObservers() {
        return obs.size();
    }
}

6.2.Observer 接口

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update() 方法,进行相应的工作。其源码如下:

package java.util;

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

6.3.案例

【例】警察抓小偷。警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:

(1)小偷是一个被观察者,所以需要继承 Observable 类

import java.util.Observable;

public class Thief extends Observable {
    private String name; public Thief(String name) {
        this.name = name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void steal() {
        System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
        super.setChanged();  //即令changed = true
        super.notifyObservers();
    }
}

(2)警察是一个观察者,所以需要让其实现 Observer 接口

import java.util.Observable;
import java.util.Observer;

public class Policemen implements Observer {
    
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Policemen(String name) {
        this.name = name;
    }
    
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
    }
}
public class Client {
    public static void main(String[] args) {
        //创建小偷对象
        Thief t = new Thief("小偷");
        //创建警察对象
        Policemen p = new Policemen("小李");
        //让警察盯着小偷
        t.addObserver(p);
        //小偷偷东西
        t.steal();
    }
}

结果如下:

小偷:我偷东西了,有没有人来抓我!!!
警察:小偷,我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!

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

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

相关文章

如何管理数据库用户

目录 一、数据库用户管理 新建用户 查询数据库中的用户 重命名用户名 删除用户 修改当前用户密码 修改其他用户密码 二、数据库用户授权 授权 允许用户在指定终端远程连接MySQL并拥有指定权限 撤销权限 授权用户权限总结 一、数据库用户管理 新建用户 命令&#x…

DP485替代MAX485 RS485/RS422 收发器芯片

DP485E 是一款 5V 供电、半双工、低功耗、低摆率&#xff0c;完全满足 TIA/EIA-485 标准要求的 RS-485收发器。DP485E 工作电压范围为 4.75~5.25V&#xff0c;具备失效安全&#xff08;fail-safe&#xff09;、过温保护、限流保护、过压保护&#xff0c;控制端口热插拔输入等功…

phoenix os在vmware workstation上的安装

一、点击创建新的虚拟机配置 选择“安装程序光盘映像文件”&#xff0c;选择你刚刚下好的PhoenixOS ISO镜像文件&#xff0c;点击下一步 分配虚拟机大小。随便你分配&#xff0c;只要大小恰当(系统大小预计会安装软件的大小3G以上的缓存及其他文件存放空间)&#xff0c;反正我就…

opencv -11 图像运算之按位逻辑运算(图像融合图像修复和去除)

按位逻辑运算是一种对图像进行像素级别的逻辑操作的方法&#xff0c;使用OpenCV的按位逻辑运算函数可以对图像进行位与&#xff08;AND&#xff09;、位或&#xff08;OR&#xff09;、位非&#xff08;NOT&#xff09;和位异或&#xff08;XOR&#xff09;等操作。 通俗点就是…

Java NIO 和 AIO 总结

title: Java NIO 和 AIO 总结 date: 2023-05-10 13:21:26 tags: NIOAIO categories:开发知识及其他 cover: https://cover.png feature: false 1. NIO Java NIO (New IO) is an alternative IO API for Java, meaning alternative to the standard Java IO and Java Networkin…

Unity Arduino 串口通信

一、Unity端发送消息&#xff0c;Arduino端接收消息 通过串口通信 Arduino端 #include <Arduino.h>#define PIN_KEY 5 uint item;void setup() {item 0;Serial.begin(115200);pinMode(PIN_KEY, OUTPUT); }void loop() {if(Serial.available()>0){item Serial.rea…

跨网络的通信过程、路由的作用以及默认网关

如下网络拓扑图&#xff0c;交换机0所在的网段为192.168.1.0/24&#xff0c;交换机1所在网段为192.168.2.0/24&#xff0c;且各自有2台主机&#xff1a; 假设PC0&#xff08;192.168.1.10/32&#xff09;要跟PC4&#xff08;192.168.2.11/32&#xff09;通信&#xff0c;如何实…

上海亚商投顾:沪指缩量调整 3D打印概念股逆势大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日缩量调整&#xff0c;午后一度均跌超1%&#xff0c;尾盘跌幅略有收窄&#xff0c;保险等权重板块走低…

【中危】Apache Airflow ODBC Provider 远程代码执行漏洞

漏洞描述 Apache Airflow 是一个开源的任务和工作流管理平台&#xff0c;ODBC Provider 是 Apache Airflow 的一个数据库管理/插件。 Apache Airflow ODBC Provider 受影响版本中&#xff0c;由于 odbc.py#driver 方法未对用户可控的 ODBC 驱动程序参数(driver)有效过滤&…

vue - 常见的性能优化

文章目录 vue使用中常见的性能优化1&#xff0c; v-for 遍历避免同时使用 v-if2&#xff0c; 如果需要使用v-for给每项元素绑定事件时 可以使用事件代理**3&#xff0c; 一些数据不做响应式4&#xff0c;一些页面采用keep-alive缓存组件5&#xff0c;第三方UI库按需导入6&#…

Selenium如何定位动态元素?

在经常做自动化过程中&#xff0c;我们没有打开新页面、没有alert、没有frame、加了挺好的等待时间&#xff0c;但是还是定位不到元素&#xff1f;很有可能是你要定位的元素的属性是动态的&#xff0c;即每次打开页面&#xff0c;这个元素的id或者class等元素属性是动态生成的。…

低代码平台缓解了程序员日渐不足的尴尬局面

编者按&#xff1a;如今即便是编程语言学习难度的降低&#xff0c;也不足以跟上计算机应用的快速发展&#xff0c;为了提高软件开发效率&#xff0c;满足市场需求&#xff0c;低代码平台的可视化开发、组件化和框架化降低了开发的技术门槛&#xff0c;让更多人能参与到软件开发…

通过SSH的方式连接Git仓库

前置条件 git已经安装。 生成公钥私钥 任意调出 git bash 执行&#xff1a; 回车两次&#xff0c;如果已有则需要覆盖确认 Overwrite ssh-keygen -t rsa -C "your_emailexample.com" 生成后的文件路径&#xff1a; C:/user/你的账户/.ssh下&#xff0c;其中 id…

麒麟信安与派盘互认证成功

麒麟信安是中国领先的信息安全技术服务商&#xff0c;具有自主研发的核心安全技术&#xff0c;致力于为企业等各类用户提供高效、安全、可靠的信息安全防护服务。而派盘是深圳科迈爱康科技有限公司的产品&#xff0c;是一款本地云存储解决方案&#xff0c;支持多平台接入&#…

WPF Prims框架详解

文章目录 前言Prism基本使用Prism选择&#xff0c;DryIoc还是UnityPrism基本框架搭建Prism动态更新View和ViewModel对应关系参数动态更新函数动态绑定 prism新建项目模板region使用事例测试是否限制空间 消息订阅如何使用消息订阅使用建议 路由导航对话框/弹窗功能实现代码 前言…

国内什么牌子的ipad手写笔好用?电容笔性价比高推荐

随着平板电脑在校园、办公室中的应用越来越广泛&#xff0c;需要一种具有良好性能的电容笔。苹果品牌原装的这支电容笔&#xff0c;虽然功能很强&#xff0c;但因为其的价格实在是太贵了&#xff0c;所以只是用来学习记笔记&#xff0c;实在是太浪费了。所以&#xff0c;哪个电…

JavaSwing+MySQL的飞机订票系统(内含oracle版本)

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88055544 JDK1.8 MySQL5.7 功能&#xff1a;接收客户端发来的数据、处理客户端发来的数据、发送数据包到客户端&#xff1b;客户端&#xff1a;查询所有航班的信息、查看自己所定的票、订票…

Day 60 小结

1.惰性学习&#xff08;消极学习&#xff09;&#xff1a;在训练数据集的时候不会创建目标函数&#xff0c;只是简单将训练样本存储。后期需要对新样本进行判断的时候分析新样本和已存储的样本之间的关系&#xff0c;并以此确定新样本的输出值。例如&#xff1a;knn算法。 2.急…

LeetCode:4. 寻找两个正序数组的中位数

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 题解目录 一、&#x1f331;[4. 寻找两个正序数组的中位数](https://leetcode.cn/proble…

微信朋友圈同步你知道怎么设置吗?

微信关于朋友圈同步 其实没有什么其他方法 但是不想一直复制粘贴 繁琐又麻烦 对于要发布很多条的情况下 就很不方便 如果是有可以同步朋友圈的功能 我们可以先选择一个好友 然后设置好跟圈任务 好友发啥你就会跟TA发出一模一样的朋友圈