设计模式之观察者模式

news2024/11/20 4:51:10

Observer design pattern

观察者模式的概念、观察者模式的结构、观察者模式的优缺点、观察者模式的使用场景、观察者模式的实现示例、观察者模式的源码分析


1、观察者模式的概念

   观察者模式,又称为发布-订阅模式,即它定义了一种对象间一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有监听这个主题对象的观察者对象,使它们能够自动更新自己。

2、观察者模式的结构

  • 抽象主题:即抽象被观察者,其定义了注册观察者、移除观察者、通知观察者的行为。
  • 具体主题:实现抽象主题,实现其定义的注册、移除、通知观察者的行为,并持有一个观察者列表,用来维护监听自身的观察者对象。
  • 抽象观察者:定义更新自身的行为。
  • 具体观察者:实现抽象观察者,实现其定义的更新自身的行为。

observer-class

3、观察者模式的优缺点

  • 优点:
    • 降低了主题与观察者的耦合度,且两者之间是抽象耦合。
    • 广播机制,即所有注册进来的观察者都会收到主题的通知。
  • 缺点:
    • 当观察者过多时,主题通知观察者会较耗时。
    • 如果主题存在循环依赖,则主题发送通知会使观察者循环调用,导致系统崩溃。

4、观察者模式的使用场景

  • 当对象间存在一对多关系,且一个对象状态的改变会影响其它对象时。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。

5、观察者模式的实现示例

抽象主题:

public interface Subject {

    /**
     * 注册观察者
     * @param observer
     */
    void addObserver(Observer observer);

    /**
     * 移除观察者
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者
     * @param message
     */
    void notifying(String message);
}

具体主题:

public class ConcreteSubject implements Subject {

    private List<Observer> observers;

    public ConcreteSubject() {
        this.observers = new ArrayList<>();
    }

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

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

    @Override
    public void notifying(String message) {
        for (Observer observer : this.observers) {
            observer.update(message);
        }
    }
}

抽象观察者:

public interface Observer {

    /**
     * 更新
     * @param message
     */
    void update(String message);
}

具体观察者:

public class OneObserver implements Observer {

    private String name;

    public OneObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println("观察者 " + this.name + " 收到消息 " + message);
    }
}

具体观察者:

public class TwoObserver implements Observer {

    private String name;

    public TwoObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println("观察者 " + this.name + " 收到消息 " + message);
    }
}

测试:

public class ObserverTest {

    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();

        subject.addObserver(new OneObserver("zed"));
        subject.addObserver(new TwoObserver("fizz"));
        subject.addObserver(new TwoObserver("ahri"));

        subject.notifying("点击左侧录制按钮 即可开始录制本局比赛");
    }
}

测试结果:

观察者 zed 收到消息 点击左侧录制按钮 即可开始录制本局比赛
观察者 fizz 收到消息 点击左侧录制按钮 即可开始录制本局比赛
观察者 ahri 收到消息 点击左侧录制按钮 即可开始录制本局比赛

6、观察者模式的源码分析

  java.util.Observer 和 java.util.Observable 这两个接口实现了观察者模式,当我们需要使用观察者模式时只需要实现这两个接口即可。

  注,Observer 和 Observable 在新版本中已被弃用,因为其支持的事件模型非常有限,且在多线程环境下会出现问题,所以设计者希望开发者使用 java.util.concurrent 包中的并发数据结构,以确保在线程间实现可靠有序的消息传递。且可以参考 java.util.concurrent.Flow API。

  Observer 接口是抽象观察者,定义了更新自身状态的方法。

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an {@code Observable} object's
     * {@code notifyObservers} 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}
     *                 method.
     */
    void update(Observable o, Object arg);
}

  Observable 接口是抽象主题,即抽象被观察者。它内部用一个 Vector 集合来维护订阅它的观察者,并定义了注册、移除、通知观察者的方法,同时,它还维护了一个 changed 状态,用来表示当前主题是否发生改变,当其为 true 时表示当前主题已发生变化,此时才会通过订阅其的观察者。

public class Observable {
    private boolean changed = false;   // 主题是否改变的状态
    private Vector<Observer> obs;   // 观察者列表

    /** Construct an Observable with zero Observers. */

    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) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            // 当 changed 为 false 时会返回 即不会通知观察者
            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();
    }
}

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

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

相关文章

spark 运行自带样例SparkPi、spark-examples报错

报错时我使用的环境如下&#xff1a; windows10中运行&#xff0c;非linux虚拟机 使用微软的Terminal软件进入powershell环境 scala 2.12.10 spark-3.1.1-bin-hadoop3.2 没有单独安装hadoop环境 java 8 注意一&#xff1a;该spark-3.1.1-bin-hadoop3.2在centos 7、树莓派4b官方…

2小时上车AI作画_NovelAI (学会能做游戏mod)

最近在打牌&#xff08;杀戮尖塔真好玩&#xff09;&#xff0c;玩着突发奇想&#xff1a; 能不能&#xff1f;把游戏原画&#xff0c;通过AI作画&#xff0c;替换为二次元风格&#xff1f; 试试就逝逝...简单复盘下 一、部署"NovelAI" 本地部署【需要本地显卡】 …

Linux C 链接模块

静态链接 Linux 下静态库的创建和使用 1.编译静态库源码&#xff1a;gcc -c lib.c -o lib.o 2.生成静态库文件&#xff1a;ar -q lib.a lib.o 2.使用静态库编译&#xff1a;gcc main.c lib.a -o main.out #20-1.c #include <stdio.h>extern char* name(); extern int a…

Python 自动化测试(三): pytest 参数化测试用例构建

在之前的文章中主要分享了 pytest 的实用特性&#xff0c;接下来讲 Pytest 参数化用例的构建。 如果待测试的输入与输出是一组数据&#xff0c;可以把测试数据组织起来用不同的测试数据调用相同的测试方法。参数化顾名思义就是把不同的参数&#xff0c;写到一个集合里&#xff…

Mentor-dft 学习笔记 day46-Graybox OverviewTessent On-Chip Clock Controller(1)

graybox功能简化了分层设计中的扫描插入和ATPG处理过程&#xff0c;允许对子模块执行扫描和ATPG操作&#xff0c;然后允许在以下情况下使用该子模块的简化灰箱表示在下一个更高层次执行扫描和ATPG操作。由于子模块的灰盒表示仅包含最小数量的互连电路&#xff0c;因此在大型分层…

Oracle-在线重定义dbms_redefinition.sync_interim_table增量同步引发TX行锁问题

前言: 近期处理了一起用户使用在线重定义dbms_redefinition增量同步操作引发TX行锁的问题&#xff0c;用户在使用dbms_redefinition.sync_interim_table进行数据增量同步时&#xff0c;在线重定义的原表SQL语句出现了TX行锁等待问题 后面经过分析&#xff0c;发现产生TX行锁问…

短链接业务解决方案(附源码项目)

开源地址 https://github.com/lcy19930619/short-link 一个单节点短链接项目&#xff0c;有需要的拿去改改就行了&#xff0c;如果方便&#xff0c;可以帮忙点点star 什么是短链接 蓝色部分就是短链接 为什么要用短链接&#xff1f; 因为短信是按照字符去计算条数的&#x…

12月21日 OpenCV 实战基础学习笔记——背景建模、光流估计

文章目录前言一、背景建模1、帧差法2、混合高斯模型二、光流估计前言 本文为12月21日 OpenCV 实战基础学习笔记&#xff0c;分为两个章节&#xff1a; 背景建模&#xff1b;光流估计。 一、背景建模 1、帧差法 由于场景中的目标在运动&#xff0c;目标的影像在不同图像帧中…

Redis哨兵机制以及发布订阅

Redis哨兵机制1 哨兵Sentinel机制2 哨兵架构原理3 搭建哨兵架构4 通过springboot操作哨兵Redis发布订阅1 哨兵Sentinel机制 Sentinel&#xff08;哨兵&#xff09;是Redis 的高可用性解决方案&#xff1a;由一个或多个Sentinel 实例组成的Sentinel 系统可以监视任意多个主服务…

海格里斯HEGERLS深度解析|重型四向穿梭车的轨道换向组件及轨道系统

随着自动化仓储物流系统的广泛应用&#xff0c;物流设备也更趋于多样化&#xff0c;比如在货架轨道上可四向行走的穿梭车应运而生&#xff0c;重型四向穿梭车作为一种新型的物流存储设备&#xff0c;通常在轨道平面上有行走方向相互垂直的两个行走系统&#xff0c;通过两个行走…

gRPC学习Go版(一)

文章目录微服务入门gRPC是什么proto 服务定义gRPC 优势gRPC入门简单使用一元RPC服务流RPC客户流RPC双工流RPCgRPC底层原理RPC流长度前缀的消息分帧请求消息响应信息通信模式下的消息流微服务入门 现在的软件很少是一个孤立的单体应用运行的&#xff0c;相反更多是通过互联网连接…

玩以太坊链上项目的必备技能(错误处理以及异常-Solidity之旅十四)

错误处理 作为开发者的我们知道&#xff0c;我们所编写出来的程序难免会出现 bug &#xff0c;而要做的是捕获异常&#xff0c;给用户抛出一个友好地错误提示。 而在 Solidity 中&#xff0c;根据状态恢复异常来处理错误&#xff0c;该异常将撤销在当前调用中对状态所做的所有…

[思维模式-9]:《如何系统思考》-5- 认识篇 - 改变开环、组合逻辑的线性思考,实施闭环、时序逻辑的动态思考。

目录 第1章 因果关系 1.1 因果关系 1.2 因果关系的特点 1.3 因果关系的类型 第2章 线性思考遇到的问题&#xff1a;开环思维、组合逻辑 2.1 开环系统 2.2 组合逻辑 2.3 线性关系 2.4 什么是线性思维&#xff1a;线性因果关系 2.5 线性思维的数学本质 2.6 线性思维的…

自动化药房出药升降机选型设计

一、 运动规划、运动参数的确定 1、 运动参数计算 运动参数主要通过速度规划确定&#xff0c;速度规划采用直线速度特性&#xff0c;如图所示。 运动方程为&#xff1a; 2、 X方向的速度和加速度的估算 已知参数&#xff1a; X方向行程:1…

stream_component_open函数分析

stream_component_open() 函数主要作用是打开 音频流或者视频流 对应的解码器&#xff0c;开启解码线程去解码。 流程图如下&#xff1a; stream_component_open() 的函数定义如下&#xff1a; /* open a given stream. Return 0 if OK */ static int stream_component_open(…

K8S知识点及dashboard操作

1.什么是K8S&#xff1f; K8S是一组服务器集群&#xff0c;可以在集群的各个节点上运行特定的容器。 K8S所管理的是&#xff1a;集群节点上的容器 特性&#xff1a; 自我修复&#xff0c;弹性伸缩&#xff08;根据实时服务器的并打情况&#xff0c;增加或收缩容器数量&…

网络编程套接字Socket(通过两个用例,逐行注释,详细理解)干活满满建议收藏

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1.分类1.流套接字2.数据报套接字3.原始套接字2.Socket通信模型 3.UDP套接字编程1. DatagramSocket API1.构造方法1.DatagramSocket()2.DatagramSocket(int port)…

C语言之复合类型上卷(十八)(阴阳两极)

上一篇: C语言之内存管理&#xff08;十七&#xff09;&#xff08;转世灵童现世&#xff09; 逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、什么是结构体&#xff1f;二、结构体的定义及初始化…

USB TO SPI(上海同旺电子)调试器调试MCP3201 A/D 转换器

所需设备&#xff1a; 1、USB TO SPI(上海同旺电子)&#xff1b; 2、MCP3201 12 位A/D 转换器; 特性 • 12 位分辨率 • 1 LSB DNL &#xff08;最大值&#xff09; • 1 LSB INL &#xff08;最大值&#xff09;&#xff08;MCP3201-B&#xff09; • 2 LSB INL &#xff…

pdf文件太大怎么变小,如何压缩pdf大小

pdf文件太大怎么变小&#xff1f;如果你是Windows电脑&#xff0c;可以使用PDF编辑器来减小PDF文件的大小&#xff0c;比如这款出色的PDF压缩工具-易我PDF编辑器&#xff0c;它的“压缩”功能提供了两种减小文件大小的方法&#xff0c;这使得它既适合那些只想获得更小的PDF的人…