【再谈设计模式】观察者模式~对象间依赖关系的信使

news2025/4/5 8:36:03

一、引言

        在软件工程、软件开发的世界里,设计模式如同建筑蓝图中的经典结构,帮助开发者构建更加灵活、可维护和可扩展的软件系统。观察者模式就是其中一种极为重要的行为型设计模式,它在处理对象间的一对多关系时展现出独特的魅力。

二、定义与描述

        观察者模式定义了对象之间的一种一对多依赖关系。其中有一个被观察的对象(称为主题Subject)和多个观察该对象的观察者(Observer)。主题对象负责维护一组观察者对象,并在自身状态发生改变时通知所有观察者。这种模式使得对象之间的耦合度降低,主题和观察者可以独立地进行扩展和修改。

三、抽象背景

        在很多实际的软件场景中,存在着对象状态变化需要通知其他对象的需求。例如,在一个新闻发布系统中,当有新的新闻发布(新闻对象状态改变)时,订阅了该新闻频道的用户(观察者)需要及时得到通知;或者在一个游戏开发中,当游戏角色的某些属性(如生命值、位置等)发生变化时,与之相关的UI界面元素(观察者)需要更新显示。

四、适用场景与现实问题解决

  • 事件驱动系统
    • 在图形用户界面(GUI)开发中,用户的操作(如点击按钮、输入文本等)会触发事件。这些事件可以看作是主题的状态变化,而处理这些事件的各个组件(如菜单更新、数据显示等)就是观察者。通过观察者模式,可以很方便地实现事件的分发和处理,使不同的组件能够独立地响应事件。

  • 股票市场监测
    • 当股票价格发生变化(主题状态改变)时,多个投资者(观察者)需要得到通知以便做出相应的决策。使用观察者模式可以高效地实现这种通知机制,而不需要在股票价格变化的代码中硬编码每个投资者的通知逻辑。

五、观察者模式的现实生活的例子

  • 社交媒体平台
    • 当一个用户(主题)发布了一条新的动态时,他的好友(观察者)会收到通知。这里,用户是被观察的对象,好友们是观察者。社交平台负责维护好友关系(即主题中的观察者列表),并在用户发布新动态时通知所有好友。

  • 气象站与订阅者
    • 气象站(主题)负责收集气象数据并检测天气状态的变化。当天气状态发生变化(如温度、湿度、气压等数据变化)时,气象站会通知所有订阅了气象信息的用户(观察者),如农民、飞行员、户外运动爱好者等。

六、初衷与问题解决

  • 初衷
    • 观察者模式的初衷是为了实现对象之间的松耦合关系。在没有这种模式的情况下,如果一个对象的状态变化需要通知其他对象,可能会导致高度耦合的代码,即变化的对象需要直接调用其他对象的方法来通知它们。这使得代码难以维护和扩展,因为任何一个相关对象的改变都可能影响到其他对象。
  • 问题解决
    • 通过观察者模式,主题和观察者之间通过抽象的接口进行交互。主题只需要维护一个观察者列表,并在状态变化时调用观察者的抽象通知方法。这样,主题不需要知道具体的观察者类型,观察者也不需要知道主题的具体实现细节。当有新的观察者或主题需要加入系统时,只需要实现相应的接口即可,不会影响到其他部分的代码。

七、代码示例

Java示例

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

// 观察者接口
interface Observer {
    void update(String message);
}

// 主题类
class Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void setState(String state) {
        this.state = state;
        notifyAllObservers();
    }

    private void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

// 具体观察者类
class ConcreteObserver implements Observer {
    private String name;

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

    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + message);
    }
}

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

        Observer observer1 = new ConcreteObserver("Observer 1");
        Observer observer2 = new ConcreteObserver("Observer 2");

        subject.attach(observer1);
        subject.attach(observer2);

        subject.setState("New state!");
    }
}

类图:

  Subject类与Observer接口之间是一对多的关系(Subject可以有多个ObserverConcreteObserver类实现了Observer接口。 

C++示例

#include <iostream>
#include <vector>

// 观察者抽象类
class Observer {
public:
    virtual void update(std::string message) = 0;
};

// 主题类
class Subject {
private:
    std::vector<Observer*> observers;
    std::string state;
public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    void detach(Observer* observer) {
        for (auto it = observers.begin(); it!= observers.end(); ++it) {
            if (*it == observer) {
                observers.erase(it);
                break;
            }
        }
    }

    void setState(std::string state) {
        this.state = state;
        notifyAllObservers();
    }

    void notifyAllObservers() {
        for (auto observer : observers) {
            observer->update(state);
        }
    }
};

// 具体观察者类
class ConcreteObserver : public Observer {
private:
    std::string name;
public:
    ConcreteObserver(std::string name) : name(name) {}

    void update(std::string message) override {
        std::cout << name << " received message: " << message << std::endl;
    }
};

int main() {
    Subject subject;

    Observer* observer1 = new ConcreteObserver("Observer 1");
    Observer* observer2 = new ConcreteObserver("Observer 2");

    subject.attach(observer1);
    subject.attach(observer2);

    subject.setState("New state!");

    return 0;
}

Python示例

# 观察者抽象类
class Observer:
    def update(self, message):
        pass

# 主题类
class Subject:
    def __init__(self):
        self.observers = []
        self.state = None

    def attach(self, observer):
        self.observers.append(observer)

    def detach(self, observer):
        self.observers.remove(observer)

    def setState(self, state):
        self.state = state
        self.notifyAllObservers()

    def notifyAllObservers(self):
        for observer in self.observers:
            observer.update(self.state)


# 具体观察者类
class ConcreteObserver(Observer):
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f"{self.name} received message: {message}")


if __name__ == "__main__":
    subject = Subject()

    observer1 = ConcreteObserver("Observer 1")
    observer2 = ConcreteObserver("Observer 2")

    subject.attach(observer1)
    subject.attach(observer2)

    subject.setState("New state!")

Go示例

package main

import (
    "fmt"
)

// 观察者接口
type Observer interface {
    update(message string)
}

// 主题结构体
type Subject struct {
    observers []Observer
    state     string
}

// 附加观察者
func (s *Subject) attach(observer Observer) {
    s.observers = append(s.observers, observer)
}

// 分离观察者
func (s *Subject) detach(observer Observer) {
    for i, obs := range s.observers {
        if obs == observer {
            s.observers = append(s.observers[:i], s.observers[i+1:]...)
            break
        }
    }
}

// 设置状态并通知观察者
func (s *Subject) setState(state string) {
    s.state = state
    s.notifyAllObservers()
}

// 通知所有观察者
func (s *Subject) notifyAllObservers() {
    for _, observer := range s.observers {
        observer.update(s.state)
    }
}

// 具体观察者结构体
type ConcreteObserver struct {
    name string
}

// 具体观察者实现更新方法
func (co *ConcreteObserver) update(message string) {
    fmt.Printf("%s received message: %s\n", co.name, message)
}

func main() {
    subject := Subject{}

    observer1 := ConcreteObserver{name: "Observer 1"}
    observer2 := ConcreteObserver{name: "Observer 2"}

    subject.attach(&observer1)
    subject.attach(&observer2)

    subject.setState("New state!")
}

八、观察者模式的优缺点

  • 优点
    • 松耦合:主题和观察者之间是松耦合的关系。主题不需要知道观察者的具体实现,只需要调用观察者的抽象接口。这使得在系统中添加或删除观察者非常容易,不会影响到主题的代码。
    • 可扩展性:可以很容易地增加新的观察者,只需要实现观察者接口即可。同样,主题也可以在不影响观察者的情况下进行扩展。
    • 支持广播通信:一个主题可以通知多个观察者,实现了一对多的消息传递,适合于需要将信息广播给多个对象的场景。
  • 缺点
    • 可能存在通知顺序问题:如果有多个观察者,当主题通知观察者时,可能会存在通知顺序不确定的问题。这在某些对顺序有严格要求的场景下可能会导致问题。
    • 性能开销:如果观察者数量较多,当主题状态发生变化时,通知所有观察者可能会带来一定的性能开销。特别是在观察者的更新操作比较复杂时,这种开销会更加明显。

九、观察者模式的升级版

  • 事件委托模型
    • 在传统的观察者模式中,主题直接通知所有的观察者。而在事件委托模型中,引入了事件源、事件和事件处理程序的概念。事件源(类似于主题)产生事件,事件包含了关于状态变化的信息,事件处理程序(类似于观察者)负责处理事件。事件委托模型更加灵活,可以根据事件的类型、优先级等因素来决定如何处理事件,而不是简单地通知所有观察者。
  • 反应式编程中的观察者模式扩展
    • 在反应式编程(如RxJava、ReactiveX等)中,观察者模式得到了进一步的扩展。反应式编程关注的是数据的流动和异步处理。在这种模式下,观察者可以对数据的变化做出反应,并且可以组合、转换和过滤数据。例如,在RxJava中,可以使用操作符来对数据流进行操作,然后再将处理后的结果通知给观察者。这使得观察者模式在处理异步和复杂的数据处理场景时更加高效和灵活。

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

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

相关文章

如何设计一个注册中心?以Zookeeper为例

这是小卷对分布式系统架构学习的第8篇文章&#xff0c;在写第2篇文章已经讲过服务发现了&#xff0c;现在就从组件工作原理入手&#xff0c;讲讲注册中心 以下是面试题&#xff1a; 某团面试官&#xff1a;你来说说怎么设计一个注册中心&#xff1f; 我&#xff1a;注册中心嘛&…

【Unity3D】导出Android项目以及Java混淆

Android Studio 下载文件归档 | Android Developers Android--混淆配置&#xff08;比较详细的混淆规则&#xff09;_android 混淆规则-CSDN博客 Unity版本&#xff1a;2019.4.0f1 Gradle版本&#xff1a;5.6.4&#xff08;或5.1.1&#xff09; Gradle Plugin版本&#xff…

2024 China Collegiate Programming Contest (CCPC) Zhengzhou Onsite 基础题题解

今天先发布基础题的题解&#xff0c;明天再发布铜牌题和银牌题的题解 L. Z-order Curve 思路&#xff1a;这题目说了&#xff0c;上面那一行&#xff0c;只有在偶数位才有可能存在1&#xff0c;那么一定存在这样的数&#xff0c;0 ,1,100, 10000,那么反之&#xff0c;我们的数…

【FlutterDart】tolyui_feedback组件例子效果(23 /100)

上效果图 有12种位置展示效果&#xff1b;很能满足大部分需要 代码如下&#xff1a; import package:flutter/material.dart; import package:tolyui_feedback/tolyui_feedback.dart;class TolyTooltipDemo extends StatelessWidget {const TolyTooltipDemo({super.key});ove…

服务器攻击方式有哪几种?

随着互联网的快速发展&#xff0c;网络攻击事件频发&#xff0c;已泛滥成互联网行业的重病&#xff0c;受到了各个行业的关注与重视&#xff0c;因为它对网络安全乃至国家安全都形成了严重的威胁。面对复杂多样的网络攻击&#xff0c;想要有效防御就必须了解网络攻击的相关内容…

Mermaid 使用教程之流程图 - 从入门到精通

本文由 Mermaid中文文档 整理而来&#xff0c;并且它同时提供了一个Mermaid在线编辑器。 Mermaid 流程图 - 基本语法​ 流程图由节点&#xff08;几何形状&#xff09;和边&#xff08;箭头或线&#xff09;组成。Mermaid代码定义了如何创建节点和边&#xff0c;并适应不同的…

Flink系统知识讲解之:如何识别反压的源头

Flink系统知识之&#xff1a;如何识别反压的源头 什么是反压 Ufuk Celebi 在一篇古老但仍然准确的文章中对此做了很好的解释。如果您不熟悉这个概念&#xff0c;强烈推荐您阅读这篇文章。如果想更深入、更低层次地了解该主题以及 Flink 网络协议栈的工作原理&#xff0c;这里有…

网络-ping包分析

-a&#xff1a;使 ping 在收到响应时发出声音&#xff08;适用于某些操作系统&#xff09;。-b&#xff1a;允许向广播地址发送 ping。-c count&#xff1a;指定发送的 ping 请求的数量。例如&#xff0c;ping -c 5 google.com 只发送 5 个请求。-i interval&#xff1a;指定两…

国产linux系统(银河麒麟,统信uos)使用 PageOffice 实现后台生成单个PDF文档

PageOffice 国产版 &#xff1a;支持信创系统&#xff0c;支持银河麒麟V10和统信UOS&#xff0c;支持X86&#xff08;intel、兆芯、海光等&#xff09;、ARM&#xff08;飞腾、鲲鹏、麒麟等&#xff09;、龙芯&#xff08;LoogArch&#xff09;芯片架构。 PageOffice 版本&…

Win10微调大语言模型ChatGLM2-6B

在《Win10本地部署大语言模型ChatGLM2-6B-CSDN博客》基础上进行&#xff0c;官方文档在这里&#xff0c;参考了这篇文章 首先确保ChatGLM2-6B下的有ptuning AdvertiseGen下载地址1&#xff0c;地址2&#xff0c;文件中数据留几行 模型文件下载地址 &#xff08;注意&#xff1…

Windows11环境下设置MySQL8字符集utf8mb4_unicode_ci

1.关闭MySQL8的服务CTRLshiftESC&#xff0c;找到MySQL关闭服务即可 2.找到配置文件路径&#xff08;msi版本默认&#xff09; C:\ProgramData\MySQL\MySQL Server 8.0 3.使用管理员权限编辑my.ini文件并保存 # Other default tuning values # MySQL Server Instance Config…

js代理模式

允许在不改变原始对象的情况下&#xff0c;通过代理对象来访问原始对象。代理对象可以在访问原始对象之前或之后&#xff0c;添加一些额外的逻辑或功能。 科学上网过程 一般情况下,在访问国外的网站,会显示无法访问 因为在dns解析过程,这些ip被禁止解析,所以显示无法访问 引…

vue3 + ts + element-plus(el-upload + vuedraggable实现上传OSS并排序)

这里创建项目就不多说了 安装element-plus npm install element-plus 安装vuedraggable npm install vuedraggable 安装ali-oss npm install ali-oss 这里是封装一下&#xff1a;在components创建文件夹jc-upload>jc-upload.vue 在封装的过程中遇到了一个问题就是dr…

理解Unity脚本编译过程:程序集

https://docs.unity3d.com/Manual/script-compilation.html 关于Unity C#脚本编译的细节&#xff0c;其中一个比较重要的知识点就是如何自定义Assembly。 预定义的assembly 默认情况下&#xff0c;Unity会按照这个规则进行编译。 PhaseAssembly nameScript files1Assembly-…

设计模式 行为型 责任链模式(Chain of Responsibility Pattern)与 常见技术框架应用 解析

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许将请求沿着处理者链进行发送。每个处理者对象都有机会处理该请求&#xff0c;直到某个处理者决定处理该请求为止。这种模式的主要目的是避免请求的发送者和接收者之间…

VS2022如何修改我们新建工程打开新建文件中,默认输入我们的main函数和宏定义

1.右击我们的VS环境&#xff0c;选择【打开文件位置】 2. 进入C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE 目录 进入【VC】文件夹 进入【VCProjectItems】文件夹 3. 修改newcfile.cpp文件 右击选择【打开方式】选择【记事本】 添加如下内容 #defi…

2025-1-10-sklearn学习(36、37) 数据集转换-无监督降维+随机投影 沙上并禽池上暝。云破月来花弄影。

文章目录 sklearn学习(36、37) 数据集转换-无监督降维随机投影sklearn学习(36) 数据集转换-无监督降维36.1 PCA: 主成份分析36.2 随机投影36.3 特征聚集 sklearn学习(37) 数据集转换-随机投影37.1 Johnson-Lindenstrauss 辅助定理37.2 高斯随机投影37.3 稀疏随机矩阵 sklearn学…

openssl编译

关于windows下&#xff0c;openssl编译 环境准备 安装 perl:https://djvniu.jb51.net/200906/tools/ActivePerl5_64.rar安装nasm&#xff1a;https://www.nasm.us/pub/nasm/releasebuilds/2.13.01/win64/nasm-2.13.01-installer-x64.exe下载opensll源码&#xff1a;https://o…

2025-1-9 QT 使用 QXlsx库 读取 .xlsx 文件 —— 导入 QXlsx库以及读取 .xlsx 的源码 实践出真知,你我共勉

文章目录 1. 导入QXlsx库2. 使用 QXlsx库 读取 .xlsx 文件小结 网上有很多教程&#xff0c;但太费劲了&#xff0c;这里有个非常简便的好方法&#xff0c;分享给大家。 1. 导入QXlsx库 转载链接 &#xff1a;https://github.com/QtExcel/QXlsx/blob/master/HowToSetProject.md…

先辑芯片HPM5300系列之SEI多摩川协议命令表问题研究

多摩川协议有9条命令&#xff0c;但是先辑SEI的命令表只有8张。0-6是可用的&#xff0c;第7张是黑洞表&#xff0c;所以只有7张可用。 命令表的限制颇多&#xff0c;比如命令表只能按顺序使用 &#xff1a;例如0、1、3&#xff0c;那么命令表3是不能用的。 如果想要实现9个命令…