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

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 designpatternimport ("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 mainimport ("fmt""sync"
)// 新闻类型
type NewsType intconst (Business NewsType = iotaTechnologySportsWorldEntertainment
)// 观察者接口
type Observer interface {Update(News)
}// 具体观察者结构体
type Subscriber struct {Name       stringInterests  map[NewsType]boolRegister   chan NewsTypeUnregister 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 <- interests.Interests[interest] = true
}func (s *Subscriber) UnregisterInterest(interest NewsType) {s.Unregister <- interestdelete(s.Interests, interest)
}// 主题接口
type Subject interface {Attach(Observer)Detach(Observer)Notify(string)
}// 具体主题结构体
type NewsAgency struct {observers map[Observer]boolnews       chan Newsmu         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 stringType     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 函数中,我们创建了新闻机构和两个订阅者,将订阅者的兴趣注册到新闻机构,并模拟了新闻发布。

相关文章:

  • SpringBoot使用rsa-encrypt-body-spring-boot实现接口加解密
  • Spring Boot+Debezium:解决 MySQL Binlog监听
  • 出书,是「盖你自己的房子」你知道吗?
  • 清华新突破||新研究揭示多智能体协作的秘密武器
  • springboot + Vue前后端项目(第十一记)
  • ArcGIS中离线发布路径分析服务,并实现小车根据路径进行运动
  • 【Spring Boot】在项目中使用Spring AI
  • Vue.js功能实现博客
  • Golang使用HTTP框架zdpgo_resty实现文件下载
  • [Linux打怪升级之路]-进程和线程
  • Web基础考点
  • vue中axios的使用
  • faster_whisper语音识别
  • jvm的类加载
  • 『USB3.0Cypress』FPGA开发(3)GPIF II短包零包时序分析
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • eclipse的离线汉化
  • java2019面试题北京
  • Next.js之基础概念(二)
  • SQLServer之创建显式事务
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 创建一种深思熟虑的文化
  • 基于HAProxy的高性能缓存服务器nuster
  • 记一次和乔布斯合作最难忘的经历
  • 判断客户端类型,Android,iOS,PC
  • 如何在 Tornado 中实现 Middleware
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 走向全栈之MongoDB的使用
  • 国内开源镜像站点
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #QT(TCP网络编程-服务端)
  • #微信小程序(布局、渲染层基础知识)
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • (2)(2.10) LTM telemetry
  • (30)数组元素和与数字和的绝对差
  • (leetcode学习)236. 二叉树的最近公共祖先
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (生成器)yield与(迭代器)generator
  • (十) 初识 Docker file
  • (推荐)叮当——中文语音对话机器人
  • (限时免费)震惊!流落人间的haproxy宝典被找到了!一切玄妙尽在此处!
  • (一)RocketMQ初步认识
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • (一)项目实践-利用Appdesigner制作目标跟踪仿真软件
  • (一一四)第九章编程练习
  • (游戏设计草稿) 《外卖员模拟器》 (3D 科幻 角色扮演 开放世界 AI VR)
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • (转)http协议
  • (转载)Linux网络编程入门
  • (自适应手机端)行业协会机构网站模板
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .Net - 类的介绍
  • .NET Core使用NPOI导出复杂,美观的Excel详解