观察者模式
什么是观察者
观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
结构
- **Subject(主题):**保持一个观察者列表,提供添加、删除和通知观察者的方法。
- **Observer(观察者):**定义一个更新接口,使得在主题状态变化时得到通知。
- Concrete Subject(具体主题):实现Subject接口,存储状态,当状态发生改变时,通知所有观察者。
- Concrete Observer(具体观察者):实现Observer接口,根据主题更新来更新自己的状态。
基本流程
- 注册观察者:观察者向主题注册自己。
- 状态变更:主题的状态发生变化。
- 通知观察者:主题通过调用注册的观察者的方法来通知它们状态已变化。
- 更新观察者:观察者接收到通知后更新自己的状态。
优点
- 解耦:观察者模式能够将主题和观察者解耦,它们之间不需要知道对方的存在。
- 可扩展性:新增观察者时,不需要修改主题的代码,符合开闭原则。
- 动态交互:可以实现动态的交互,主题可以在运行时添加或删除观察者。
缺点
- 循环引用:如果不当使用,可能会导致循环引用,增加内存管理的难度。
- 性能问题:当观察者较多时,通知所有观察者可能会造成性能问题。
- 顺序不确定:观察者接收通知的顺序是不确定的,可能会导致不可预知的副作用。
使用场景
观察者模式通常用于构建松耦合的系统,其中一个对象(称为主题或发布者)可以通知多个其他对象(称为观察者或订阅者)关于状态的变化。
-
在线购物平台订单管理:
- 主题(Subject):订单系统,负责在订单状态更新时(如确认、发货、收货)广播变更事件。
- 观察者(Observers):包括支付模块、库存管理、物流跟踪等,它们监听订单状态更新并执行相应操作。
-
图形用户界面(GUI)同步:
- 主题(Subject):文档管理系统,监控文档内容的更改并触发更新事件。
- 观察者(Observers):界面组件如文本框、滚动条、状态栏等,它们接收更新事件并刷新显示。
-
模型-视图-控制器(MVC)架构:
- 主题(Subject):数据模型,实时更新数据状态并通知视图与控制器。
- 观察者(Observers):视图界面和控制器逻辑,订阅数据变更,视图更新显示,控制器响应用户交互。
-
社交媒体内容更新:
- 主题(Subject):用户发布系统,当用户发布新推文或状态时触发通知。
- 观察者(Observers):粉丝和关注者,他们接收到新内容的通知并更新自己的信息流。
-
股票交易实时系统:
- 主题(Subject):股票行情中心,实时监控并发布股票价格的变动。
- 观察者(Observers):交易平台界面、分析工具、自动交易脚本等,它们根据行情变化进行决策和操作。
-
动态配置更新系统:
- 主题(Subject):配置服务器,负责维护应用配置并在配置更新时发送通知。
- 观察者(Observers):应用服务和组件,它们监听配置变更并实时调整自身设置。
注意事项
- 避免循环引用:确保主题和观察者之间不会产生循环引用。
- 管理生命周期:合理管理主题和观察者的生命周期,避免内存泄漏。
- 线程安全:在多线程环境中使用观察者模式时,需要考虑线程安全问题
代码案例
package designpattern
import (
"fmt"
"sync"
)
// Observer 观察者接口
type Observer interface {
Update() // Update方法用于接收主题状态变化的通知
}
// ConcreteObserver 具体观察者
type ConcreteObserver struct {
name string
}
func (c *ConcreteObserver) Update() {
fmt.Printf("%s is notified.\n", c.name) // 具体观察者接收到通知后的具体处理逻辑
}
// Subject 主题接口
type Subject interface {
RegisterObserver(observer Observer) // 注册观察者
DeregisterObserver(observer Observer) // 注销观察者
NotifyObservers() // 通知所有观察者
}
// ConcreteSubject 具体主题
type ConcreteSubject struct {
observers []Observer // 观察者列表
state int // 主题状态
mu sync.Mutex // 互斥锁,用于保护并发访问
}
// NewConcreteSubject 创建具体主题实例
func NewConcreteSubject() *ConcreteSubject {
return &ConcreteSubject{
observers: make([]Observer, 0),
mu: sync.Mutex{}, // 初始化互斥锁
}
}
func (cs *ConcreteSubject) RegisterObserver(observer Observer) {
cs.mu.Lock()
defer cs.mu.Unlock()
cs.observers = append(cs.observers, observer) // 注册观察者到列表中
}
func (cs *ConcreteSubject) DeregisterObserver(observer Observer) {
cs.mu.Lock()
defer cs.mu.Unlock()
for i, ob := range cs.observers {
if ob == observer {
cs.observers = append(cs.observers[:i], cs.observers[i+1:]...) // 从观察者列表中注销观察者
break
}
}
}
func (cs *ConcreteSubject) NotifyObservers() {
cs.mu.Lock()
defer cs.mu.Unlock()
for _, ob := range cs.observers {
ob.Update() // 通知所有观察者主题状态变化
}
}
func (cs *ConcreteSubject) SetState(state int) {
cs.mu.Lock()
defer cs.mu.Unlock()
cs.state = state // 设置主题状态
cs.NotifyObservers() // 通知所有观察者主题状态变化
}
func main() {
// 在 main 函数中演示了具体的使用方法,创建具体主题实例,注册观察者,并设置主题状态,触发通知
subject := NewConcreteSubject()
ob1 := &ConcreteObserver{"ob1"}
ob2 := &ConcreteObserver{"ob2"}
subject.RegisterObserver(ob1)
subject.RegisterObserver(ob2)
subject.SetState(1)
}
模拟一个新闻发布网站
package main
import (
"fmt"
"sync"
)
// 新闻类型
type NewsType int
const (
Business NewsType = iota
Technology
Sports
World
Entertainment
)
// 观察者接口
type Observer interface {
Update(News)
}
// 具体观察者结构体
type Subscriber struct {
Name string
Interests map[NewsType]bool
Register chan NewsType
Unregister chan NewsType
}
func NewSubscriber(name string) *Subscriber {
return &Subscriber{
Name: name,
Interests: make(map[NewsType]bool),
Register: make(chan NewsType),
Unregister: make(chan NewsType),
}
}
func (s *Subscriber) Update(news News) {
if _, ok := s.Interests[news.Type]; ok {
fmt.Printf("%s received news: %s\n", s.Name, news.Headline)
}
}
func (s *Subscriber) RegisterInterest(interest NewsType) {
s.Register <- interest
s.Interests[interest] = true
}
func (s *Subscriber) UnregisterInterest(interest NewsType) {
s.Unregister <- interest
delete(s.Interests, interest)
}
// 主题接口
type Subject interface {
Attach(Observer)
Detach(Observer)
Notify(string)
}
// 具体主题结构体
type NewsAgency struct {
observers map[Observer]bool
news chan News
mu sync.Mutex
}
func NewNewsAgency() *NewsAgency {
return &NewsAgency{
observers: make(map[Observer]bool),
news: make(chan News),
}
}
func (a *NewsAgency) Attach(observer Observer) {
a.mu.Lock()
defer a.mu.Unlock()
a.observers[observer] = true
}
func (a *NewsAgency) Detach(observer Observer) {
a.mu.Lock()
defer a.mu.Unlock()
delete(a.observers, observer)
}
func (a *NewsAgency) Notify(headline string) {
for observer, _ := range a.observers {
news := News{Headline: headline}
go observer.Update(news)
}
}
// 新闻结构体
type News struct {
Headline string
Type NewsType
}
func main() {
// 创建新闻机构
agency := NewNewsAgency()
// 创建订阅者
alice := NewSubscriber("Alice")
bob := NewSubscriber("Bob")
// 订阅兴趣
alice.RegisterInterest(Business)
alice.RegisterInterest(World)
bob.RegisterInterest(Technology)
bob.RegisterInterest(Entertainment)
// 将订阅者作为观察者注册到新闻机构
agency.Attach(alice)
agency.Attach(bob)
// 新闻发布
agency.Notify("Big Corp acquired Small Tech for $1B")
// 订阅者取消订阅
bob.UnregisterInterest(Entertainment)
// 再次新闻发布
agency.Notify("New breakthrough in AI technology")
}
- 定义了
NewsType
类型,用于区分不同类型的新闻。 -
Observer
接口有一个Update
方法,用于接收新闻更新。 -
Subscriber
结构体代表具体的观察者,它包含订阅者的名字和兴趣,以及注册和注销兴趣的通道。 -
Subject
接口包含Attach
、Detach
和Notify
方法。 -
NewsAgency
结构体代表具体的主题,它维护了一个观察者集合和一个发布新闻的通道。 -
News
结构体包含新闻的标题和类型。 - 在
main
函数中,我们创建了新闻机构和两个订阅者,将订阅者的兴趣注册到新闻机构,并模拟了新闻发布。