当前位置: 首页 > news >正文

Go语言设计模式:观察者模式详解

文章目录

    • 一、 观察者模式概述
      • 1.1 观察者模式是什么?
      • 1.2 模式核心概念
      • 1.3 UML 类图
      • 1.4 优缺点分析
    • 二、Go语言实现观察者模式
      • 2.1 步骤 1: 定义观察者和主题接口
      • 2.2 步骤 2: 实现具体主题
      • 2.3 步骤 3: 实现具体观察者
      • 2.4 步骤 4: 客户端代码与演示
    • 三、Go语言中的变体:使用 Channel
      • 3.1 核心思想
      • 3.2 简化的 Channel 版本实现

一、 观察者模式概述

1.1 观察者模式是什么?

观察者模式是一种行为设计模式,它定义了对象之间的一种一对多的依赖关系。当一个对象(被观察者/主题)的状态发生改变时,所有依赖于它的对象(观察者)都将得到通知并自动更新。这个模式也被称为“发布-订阅”模式,在很多场景下都非常实用,例如:

  • GUI事件处理:按钮被点击(主题状态改变),通知所有注册的监听器(观察者)执行相应操作。
  • 消息队列/事件总线:生产者发布消息(主题),所有订阅了该主题的消费者(观察者)都会收到消息。
  • 模型-视图-控制器:模型数据(主题)变化时,视图(观察者)自动刷新。

1.2 模式核心概念

观察者模式主要包含四个核心角色:

  1. Subject (主题/被观察者)
    • 维护一个观察者列表。
    • 提供注册(Attach)、注销(Detach)观察者的方法。
    • 当自身状态改变时,通过 Notify 方法通知所有注册的观察者。
  2. Observer (观察者)
    • 定义一个更新接口(通常是 Update 方法),当接到主题的通知时,这个方法会被调用。
  3. ConcreteSubject (具体主题)
    • 实现主题接口,存储具体的状态。
    • 当状态改变时,向其所有观察者发送通知。
  4. ConcreteObserver (具体观察者)
    • 实现观察者接口,以便在主题状态改变时能接收到通知。
    • 通常会持有一个对具体主题的引用,以便在更新时可以获取主题的最新状态。

1.3 UML 类图

"管理"
Subject
+Attach(observer Observer)
+Detach(observer Observer)
+Notify()
ConcreteSubject
-state: int
-observers: []Observer
+GetState() : int
+SetState(state int)
+Attach(observer Observer)
+Detach(observer Observer)
+Notify()
«interface»
Observer
+Update(subject Subject)
ConcreteObserverA
-name: string
+Update(subject Subject)
ConcreteObserverB
-name: string
+Update(subject Subject)

1.4 优缺点分析

优点

  1. 松耦合:主题只知道它有一系列观察者,但不知道它们是谁,如何工作。观察者也只知道它订阅了一个主题,而不需要知道主题的内部实现。
  2. 可扩展性:可以随时增加新的观察者,而无需修改主题的代码,符合“开闭原则”。
  3. 广播通信:支持一对多的广播机制,非常高效。

缺点

  1. 更新顺序不确定:如果观察者之间有依赖关系,很难保证它们的更新顺序。
  2. 可能导致意外更新:如果观察者逻辑复杂,或者观察者数量很多,一次状态改变可能引发一系列连锁的、难以预料的更新。
  3. 内存泄漏风险:如果忘记注销观察者,即使观察者对象已经不再使用,它仍然会被主题持有,导致内存无法回收。在Go中,如果观察者是一个goroutine,它可能会永远阻塞在channel上。

二、Go语言实现观察者模式

Go语言没有像Java那样的 interfaceimplements 关键字,而是通过隐式实现接口的方式。这使得Go的实现更加灵活和地道。我们将通过一个具体的例子来演示:一个气象站(ConcreteSubject)监测温度,当温度变化时,通知所有显示设备(ConcreteObserver)更新显示。

2.1 步骤 1: 定义观察者和主题接口

首先,我们定义两个核心接口。

// observer.go
package main
// Observer 观察者接口,定义了更新的方法
type Observer interface {Update(subject Subject) // 接收主题对象,以便获取其状态
}
// Subject 主题接口,定义了管理观察者的方法
type Subject interface {Register(observer Observer)Unregister(observer Observer)Notify()
}

2.2 步骤 2: 实现具体主题

WeatherStation 将是我们的具体主题,它负责管理观察者并在温度变化时发出通知。

// weather_station.go
package main
import "fmt"
// WeatherStation 具体主题:气象站
type WeatherStation struct {observers []Observertemperature float64
}
// NewWeatherStation 创建一个新的气象站
func NewWeatherStation() *WeatherStation {return &WeatherStation{observers: make([]Observer, 0),}
}
// Register 注册一个观察者
func (w *WeatherStation) Register(observer Observer) {w.observers = append(w.observers, observer)fmt.Printf("观察者已注册: %T\n", observer)
}
// Unregister 注销一个观察者
func (w *WeatherStation) Unregister(observer Observer) {for i, obs := range w.observers {if obs == observer {w.observers = append(w.observers[:i], w.observers[i+1:]...)fmt.Printf("观察者已注销: %T\n", observer)break}}
}
// Notify 通知所有注册的观察者
func (w *WeatherStation) Notify() {fmt.Println("\n气象站状态变化,通知所有观察者...")for _, observer := range w.observers {observer.Update(w)}
}
// SetTemperature 设置温度,并在变化时通知观察者
func (w *WeatherStation) SetTemperature(temp float64) {if w.temperature != temp {w.temperature = tempfmt.Printf("温度更新为: %.1f°C\n", w.temperature)w.Notify()}
}
// GetTemperature 获取当前温度
func (w *WeatherStation) GetTemperature() float64 {return w.temperature
}

2.3 步骤 3: 实现具体观察者

我们创建两种不同的显示设备作为观察者。

// displays.go
package main
import "fmt"
// TemperatureDisplay 具体观察者:温度显示器
type TemperatureDisplay struct {name string
}
func NewTemperatureDisplay(name string) *TemperatureDisplay {return &TemperatureDisplay{name: name}
}
// Update 实现观察者接口的更新方法
func (t *TemperatureDisplay) Update(subject Subject) {// 类型断言,确保我们处理的是 WeatherStationif ws, ok := subject.(*WeatherStation); ok {fmt.Printf("[%s] 当前温度: %.1f°C\n", t.name, ws.GetTemperature())}
}
// FanController 具体观察者:风扇控制器
type FanController struct {name string
}
func NewFanController(name string) *FanController {return &FanController{name: name}
}
// Update 实现观察者接口的更新方法
func (f *FanController) Update(subject Subject) {if ws, ok := subject.(*WeatherStation); ok {temp := ws.GetTemperature()if temp > 25.0 {fmt.Printf("[%s] 温度超过25°C,风扇已开启!\n", f.name)} else {fmt.Printf("[%s] 温度适宜,风扇已关闭。\n", f.name)}}
}

2.4 步骤 4: 客户端代码与演示

最后,我们把所有部分组合起来,看看模式是如何工作的。

// main.go
package main
func main() {// 1. 创建主题(气象站)weatherStation := NewWeatherStation()// 2. 创建观察者(显示设备)displayA := NewTemperatureDisplay("客厅显示器")displayB := NewTemperatureDisplay("卧室显示器")fanController := NewFanController("智能风扇")// 3. 注册观察者weatherStation.Register(displayA)weatherStation.Register(displayB)weatherStation.Register(fanController)// 4. 主题状态改变,触发通知fmt.Println("\n--- 第一次温度更新 ---")weatherStation.SetTemperature(22.5)// 5. 再次改变状态fmt.Println("\n--- 第二次温度更新 ---")weatherStation.SetTemperature(28.0)// 6. 注销一个观察者weatherStation.Unregister(displayB)// 7. 再次改变状态,观察通知对象的变化fmt.Println("\n--- 第三次温度更新 ---")weatherStation.SetTemperature(30.1)
}

运行结果:

观察者已注册: *main.TemperatureDisplay
观察者已注册: *main.TemperatureDisplay
观察者已注册: *main.FanController
--- 第一次温度更新 ---
温度更新为: 22.5°C
气象站状态变化,通知所有观察者...
[客厅显示器] 当前温度: 22.5°C
[卧室显示器] 当前温度: 22.5°C
[智能风扇] 温度适宜,风扇已关闭。
--- 第二次温度更新 ---
温度更新为: 28.0°C
气象站状态变化,通知所有观察者...
[客厅显示器] 当前温度: 28.0°C
[卧室显示器] 当前温度: 28.0°C
[智能风扇] 温度超过25°C,风扇已开启!
观察者已注销: *main.TemperatureDisplay
--- 第三次温度更新 ---
温度更新为: 30.1°C
气象站状态变化,通知所有观察者...
[客厅显示器] 当前温度: 30.1°C
[智能风扇] 温度超过25°C,风扇已开启!

三、Go语言中的变体:使用 Channel

3.1 核心思想

Go语言的并发特性(goroutine 和 channel)为观察者模式提供了一个更强大、更地道的实现方式,尤其适用于分布式或高并发场景。这种方式下,通知是异步的,主题和观察者之间的耦合度更低。

核心思想

  • 主题不再直接调用观察者的 Update 方法。
  • 主题维护一个 chan 切片,每个 chan 对应一个观察者。
  • 当状态改变时,主题将消息(或状态本身)发送到所有的 chan 中。
  • 每个观察者都在一个独立的 goroutine 中监听自己的 chan

3.2 简化的 Channel 版本实现

package main
import ("fmt""time"
)
// 使用 channel 作为观察者的更新管道
type UpdateChan chan float64
// WeatherStationChannel 使用 channel 的气象站
type WeatherStationChannel struct {temperature float64updateChans []UpdateChan
}
func NewWeatherStationChannel() *WeatherStationChannel {return &WeatherStationChannel{updateChans: make([]UpdateChan, 0),}
}
func (w *WeatherStationChannel) Register(ch UpdateChan) {w.updateChans = append(w.updateChans, ch)
}
func (w *WeatherStationChannel) SetTemperature(temp float64) {if w.temperature != temp {w.temperature = tempfmt.Printf("\n温度更新为: %.1f°C,异步通知中...\n", w.temperature)// 遍历所有 channel,非阻塞地发送温度for _, ch := range w.updateChans {// 使用 select 防止阻塞,如果观察者处理慢,可以跳过或缓存select {case ch <- w.temperature:default:fmt.Println("警告: 观察者处理缓慢,跳过本次更新")}}}
}
// 观察者逻辑
func displayObserver(name string, ch UpdateChan) {for temp := range ch {fmt.Printf("[%s] 收到温度更新: %.1f°C\n", name, temp)}
}
func main() {station := NewWeatherStationChannel()// 创建观察者的 channeldisplayAChan := make(UpdateChan, 1) // 带缓冲的 channel,防止阻塞displayBChan := make(UpdateChan, 1)// 注册观察者station.Register(displayAChan)station.Register(displayBChan)// 启动观察者 goroutinego displayObserver("客厅显示器", displayAChan)go displayObserver("卧室显示器", displayBChan)// 模拟温度变化station.SetTemperature(20.0)time.Sleep(500 * time.Millisecond)station.SetTemperature(25.5)time.Sleep(500 * time.Millisecond)station.SetTemperature(29.0)time.Sleep(1 * time.Second) // 等待所有观察者处理完毕
}

总结:观察者模式是Go中一个非常实用且常见的模式。

  • 对于简单、同步的场景,使用接口和结构体的传统实现方式清晰直观。
  • 对于需要异步、解耦、高性能的场景,利用Go的 channelgoroutine 是更地道、更强大的选择,它天然地支持了非阻塞通知和并发处理。
http://www.dtcms.com/a/565209.html

相关文章:

  • 什么是音频码率?音频码率128kbps怎么设置?
  • 2026版基于python大数据的旅游可视化及推荐系统
  • 企业财务智能体架构解析:从RPA自动化到AI风控协同
  • 北京seo不到首页不扣费厦门seo顾问屈兴东
  • 玩转Rust高级应用 如何进行理解Refutability(可反驳性): 模式是否会匹配失效
  • Excel怎么快速合并当前工作簿下的所有工作表?
  • 网站建设捌金手指花总十六永久链接生成器
  • Nestjs框架: 微服务事件驱动通信与超时处理机制优化基于Event-Based 通信及异常捕获实践
  • html网站建设案例杭州发布官网
  • C语言实现观察者模式
  • JAVA算法练习题day62
  • SAP PP 生产报废单传输接口分享
  • 数据结构——三十八、查找的基本概念(王道408)
  • 深蓝学院 概率图模型
  • Kanass零基础学习,如何快速导入Jira、Mantis数据
  • 漳州网站建设多少钱创业计划书模板
  • linux vscode+cmake+clangd
  • 如何在 Linux 中获取更多信息
  • equals()与hashCode()之间的关系
  • Visual Studio Code 控制台乱码问题
  • 网站实现中英文asp.net网站很快吗
  • 公司网站链接建设电影网站论文
  • nvm切换node版本时,npm不跟着切换解决
  • iOS 应用逆向对抗手段,多工具组合实战(iOS 逆向防护/IPA 混淆/无源码加固/Ipa Guard CLI 实操)
  • x86架构的Ubuntu 22系统上,备份ISO镜像
  • 死锁防范:四大条件与破解之道
  • 考研408--数据结构--day1--基础概念时间、空间复杂度
  • 网站建设服务标准自己做热图的网站
  • WordPress如何设置站点名称做摄影网站的目的
  • Git创建合并分支、多人协作