当前位置:首页 > 技术 > 正文内容

构建事件驱动架构:观察者模式的核心机制与多语言实现

访客 技术 2026年7月31日 1

观察者模式(Observer Pattern)属于行为型设计范畴,其核心在于建立一种一对多的事件联动机制。当目标对象内部状态发生流转时,所有已注册的订阅方将自动接收到变更信号并执行预设的响应逻辑。该模式广泛应用于GUI事件处理、消息队列推送以及分布式系统中的状态同步场景。

核心角色与协作流程

  • 状态持有者(Subject/Target):负责维护订阅者集合,提供注册、注销与广播接口。其内部状态的任何有效变更都会触发通知流程。
  • 状态订阅者(Observer):通常以接口或抽象类形式存在,声明统一的数据接收方法。具体实现类决定收到信号后的业务处理逻辑。

典型的交互链路如下:

  1. 订阅阶段:订阅方将自身实例注入目标对象的监听器容器中。
  2. 状态流转:目标对象通过 setter 或业务方法修改内部数据,并在变更生效后调用内部广播函数。
  3. 事件分发:广播函数遍历容器,逐个调用订阅方声明的回调方法,通常会将最新状态或事件上下文作为参数传递。
  4. 独立响应:各订阅方根据自身业务规则解析数据,完成视图刷新、日志记录或缓存更新等操作。

架构优势与潜在挑战

优势

  • 职责分离与低耦合:目标对象无需知晓订阅者的具体类型,仅需依赖抽象接口,符合依赖倒置原则。
  • 动态扩展能力:运行时可自由增删监听器,无需修改目标对象的底层代码,满足开闭原则。
  • 自动化广播机制:消除了轮询带来的资源浪费,实现基于事件驱动的即时响应。

局限性

  • 级联调用风险:若订阅方在处理逻辑中再次修改目标状态,可能触发无限递归或死锁。
  • 执行顺序不可控:默认采用线性遍历,各监听器的回调时机缺乏严格排序,可能影响依赖特定先后次序的业务。
  • 性能与内存开销:高频状态变更会引发大量函数调用;若未及时注销废弃的监听器,极易造成内存泄漏。
  • 并发安全考量:在多线程环境中,监听器容器的读写操作及广播过程需引入同步原语,否则易出现数据竞争。

多语言实现示例

Go 语言版本

以下示例模拟了任务进度追踪场景。目标对象为调度器,监听器分别负责控制台打印与日志归档。

package main

import "fmt"

// 进度监听接口
type ProgressListener interface {
    OnProgressUpdate(taskID string, percent int)
}

// 任务调度器(目标对象)
type TaskScheduler struct {
    taskID    string
    progress  int
    listeners []ProgressListener
}

func (s *TaskScheduler) Register(l ProgressListener) {
    s.listeners = append(s.listeners, l)
}

func (s *TaskScheduler) Remove(l ProgressListener) {
    for i, listener := range s.listeners {
        if listener == l {
            s.listeners = append(s.listeners[:i], s.listeners[i+1:]...)
            break
        }
    }
}

func (s *TaskScheduler) UpdateProgress(p int) {
    if p < 0 || p > 100 {
        fmt.Println("Progress value must be between 0 and 100")
        return
    }
    s.progress = p
    s.broadcast()
}

func (s *TaskScheduler) broadcast() {
    for _, l := range s.listeners {
        l.OnProgressUpdate(s.taskID, s.progress)
    }
}

// 具体监听器:控制台输出
type ConsolePrinter struct{}

func (c *ConsolePrinter) OnProgressUpdate(id string, p int) {
    fmt.Printf("[Console] Task %s: %d%% completed\n", id, p)
}

// 具体监听器:磁盘日志记录
type FileLogger struct{}

func (f *FileLogger) OnProgressUpdate(id string, p int) {
    if p == 100 {
        fmt.Printf("[FileLogger] Task %s finished. Archiving logs.\n", id)
    }
}

func main() {
    scheduler := &TaskScheduler{taskID: "JOB-001"}
    console := &ConsolePrinter{}
    logger := &FileLogger{}

    scheduler.Register(console)
    scheduler.Register(logger)

    scheduler.UpdateProgress(25)
    scheduler.UpdateProgress(75)
    scheduler.UpdateProgress(100)
    scheduler.UpdateProgress(110) // Invalid input
}

运行输出:

[Console] Task JOB-001: 25% completed
[Console] Task JOB-001: 75% completed
[Console] Task JOB-001: 100% completed
[FileLogger] Task JOB-001 finished. Archiving logs.
Progress value must be between 0 and 100

Python 语言版本

Python 实现侧重于面向对象特性,利用抽象基类规范回调行为。

from abc import ABC, abstractmethod

class ProgressListener(ABC):
    @abstractmethod
    def on_update(self, task_id: str, percentage: int):
        pass

class TaskScheduler:
    def __init__(self, tid: str):
        self._tid = tid
        self._pct = 0
        self._subs = []

    def subscribe(self, listener: ProgressListener):
        self._subs.append(listener)

    def remove(self, listener: ProgressListener):
        if listener in self._subs:
            self._subs.remove(listener)

    def set_progress(self, val: int):
        if not (0 <= val <= 100):
            raise ValueError("Progress out of bounds")
        self._pct = val
        self._notify()

    def _notify(self):
        for sub in self._subs:
            sub.on_update(self._tid, self._pct)

class StdoutViewer(ProgressListener):
    def on_update(self, tid: str, pct: int):
        print(f"Console: {tid} -> {pct}%")

class BackupTracker(ProgressListener):
    def on_update(self, tid: str, pct: int):
        if pct >= 50:
            print(f"Backup: {tid} checkpoint saved at {pct}%")

if __name__ == "__main__":
    scheduler = TaskScheduler("TASK-X")
    viewer = StdoutViewer()
    tracker = BackupTracker()

    scheduler.subscribe(viewer)
    scheduler.subscribe(tracker)

    scheduler.set_progress(30)
    scheduler.set_progress(60)
    scheduler.set_progress(100)

运行输出:

Console: TASK-X -> 30%
Console: TASK-X -> 60%
Backup: TASK-X checkpoint saved at 60%
Console: TASK-X -> 100%
Backup: TASK-X checkpoint saved at 100%
标签: 观察者模式

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。