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 模式核心概念
观察者模式主要包含四个核心角色:
- Subject (主题/被观察者): 
- 维护一个观察者列表。
 - 提供注册(
Attach)、注销(Detach)观察者的方法。 - 当自身状态改变时,通过 
Notify方法通知所有注册的观察者。 
 - Observer (观察者): 
- 定义一个更新接口(通常是 
Update方法),当接到主题的通知时,这个方法会被调用。 
 - 定义一个更新接口(通常是 
 - ConcreteSubject (具体主题): 
- 实现主题接口,存储具体的状态。
 - 当状态改变时,向其所有观察者发送通知。
 
 - ConcreteObserver (具体观察者): 
- 实现观察者接口,以便在主题状态改变时能接收到通知。
 - 通常会持有一个对具体主题的引用,以便在更新时可以获取主题的最新状态。
 
 
1.3 UML 类图
1.4 优缺点分析
优点:
- 松耦合:主题只知道它有一系列观察者,但不知道它们是谁,如何工作。观察者也只知道它订阅了一个主题,而不需要知道主题的内部实现。
 - 可扩展性:可以随时增加新的观察者,而无需修改主题的代码,符合“开闭原则”。
 - 广播通信:支持一对多的广播机制,非常高效。
 
缺点:
- 更新顺序不确定:如果观察者之间有依赖关系,很难保证它们的更新顺序。
 - 可能导致意外更新:如果观察者逻辑复杂,或者观察者数量很多,一次状态改变可能引发一系列连锁的、难以预料的更新。
 - 内存泄漏风险:如果忘记注销观察者,即使观察者对象已经不再使用,它仍然会被主题持有,导致内存无法回收。在Go中,如果观察者是一个goroutine,它可能会永远阻塞在channel上。
 
二、Go语言实现观察者模式
Go语言没有像Java那样的 interface 和 implements 关键字,而是通过隐式实现接口的方式。这使得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的 
channel和goroutine是更地道、更强大的选择,它天然地支持了非阻塞通知和并发处理。 
