Golang设计模式(四):观察者模式

观察者模式

什么是观察者

观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。

结构

  1. **Subject(主题):**保持一个观察者列表,提供添加、删除和通知观察者的方法。
  2. **Observer(观察者):**定义一个更新接口,使得在主题状态变化时得到通知。
  3. Concrete Subject(具体主题):实现Subject接口,存储状态,当状态发生改变时,通知所有观察者。
  4. Concrete Observer(具体观察者):实现Observer接口,根据主题更新来更新自己的状态。

基本流程

  1. 注册观察者:观察者向主题注册自己。
  2. 状态变更:主题的状态发生变化。
  3. 通知观察者:主题通过调用注册的观察者的方法来通知它们状态已变化。
  4. 更新观察者:观察者接收到通知后更新自己的状态。

优点

  1. 解耦:观察者模式能够将主题和观察者解耦,它们之间不需要知道对方的存在。
  2. 可扩展性:新增观察者时,不需要修改主题的代码,符合开闭原则。
  3. 动态交互:可以实现动态的交互,主题可以在运行时添加或删除观察者。

缺点

  1. 循环引用:如果不当使用,可能会导致循环引用,增加内存管理的难度。
  2. 性能问题:当观察者较多时,通知所有观察者可能会造成性能问题。
  3. 顺序不确定:观察者接收通知的顺序是不确定的,可能会导致不可预知的副作用。

使用场景

观察者模式通常用于构建松耦合的系统,其中一个对象(称为主题或发布者)可以通知多个其他对象(称为观察者或订阅者)关于状态的变化。

  1. 在线购物平台订单管理
    • 主题(Subject):订单系统,负责在订单状态更新时(如确认、发货、收货)广播变更事件。
    • 观察者(Observers):包括支付模块、库存管理、物流跟踪等,它们监听订单状态更新并执行相应操作。
  2. 图形用户界面(GUI)同步
    • 主题(Subject):文档管理系统,监控文档内容的更改并触发更新事件。
    • 观察者(Observers):界面组件如文本框、滚动条、状态栏等,它们接收更新事件并刷新显示。
  3. 模型-视图-控制器(MVC)架构
    • 主题(Subject):数据模型,实时更新数据状态并通知视图与控制器。
    • 观察者(Observers):视图界面和控制器逻辑,订阅数据变更,视图更新显示,控制器响应用户交互。
  4. 社交媒体内容更新
    • 主题(Subject):用户发布系统,当用户发布新推文或状态时触发通知。
    • 观察者(Observers):粉丝和关注者,他们接收到新内容的通知并更新自己的信息流。
  5. 股票交易实时系统
    • 主题(Subject):股票行情中心,实时监控并发布股票价格的变动。
    • 观察者(Observers):交易平台界面、分析工具、自动交易脚本等,它们根据行情变化进行决策和操作。
  6. 动态配置更新系统
    • 主题(Subject):配置服务器,负责维护应用配置并在配置更新时发送通知。
    • 观察者(Observers):应用服务和组件,它们监听配置变更并实时调整自身设置。

注意事项

  1. 避免循环引用:确保主题和观察者之间不会产生循环引用。
  2. 管理生命周期:合理管理主题和观察者的生命周期,避免内存泄漏。
  3. 线程安全:在多线程环境中使用观察者模式时,需要考虑线程安全问题

代码案例

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 接口包含 AttachDetachNotify 方法。
  • NewsAgency 结构体代表具体的主题,它维护了一个观察者集合和一个发布新闻的通道。
  • News 结构体包含新闻的标题和类型。
  • main 函数中,我们创建了新闻机构和两个订阅者,将订阅者的兴趣注册到新闻机构,并模拟了新闻发布。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值