观察者模式的实际应用

简介: 遇到一个用观察者模式解决问题的场景,和大家一起分享

前言


设计模式不管是在面试还是工作中都会遇到,但我经常碰到小伙伴抱怨实际工作中自己应用设计模式的机会非常小。


正好最近工作中遇到一个用观察者模式解决问题的场景,和大家一起分享。


背景如下:


在用户创建完订单的标准流程中需要做额外一些事情:


网络异常,图片无法展示
|


同时这些业务也是不固定的,随时会根据业务发展增加、修改逻辑。


如果直接将逻辑写在下单业务中,这一”坨“不是很核心的业务就会占据的越来越多,修改时还有可能影响到正常的下单流程。


当然也有其他方案,比如可以启动几个定时任务,定期扫描扫描订单然后实现自己的业务逻辑;但这样会浪费许多不必要的请求。


观察者模式


因此观察者模式就应运而生,它是由事件发布者在自身状态发生变化时发出通知,由观察者获取消息实现业务逻辑。


这样事件发布者和接收者就可以完全解耦,互不影响;本质上也是对开闭原则的一种实现。


示例代码


网络异常,图片无法展示
|


先大体看一下观察者模式所使用到的接口与关系:


  • 主体接口:定义了注册实现、循环通知接口。


  • 观察者接口:定义了接收主体通知的接口。


  • 主体、观察者接口都可以有多个实现。


  • 业务代码只需要使用 Subject.Nofity() 接口即可。


接下来看看创建订单过程中的实现案例。


代码采用 go 实现,其他语言也是类似。


首先按照上图定义了两个接口:


type Subject interface {
  Register(Observer)
  Notify(data interface{})
}
type Observer interface {
  Update(data interface{})
}


由于我们这是一个下单的事件,所以定义了 OrderCreateSubject 实现 Subject


type OrderCreateSubject struct {
  observerList []Observer
}
func NewOrderCreate() Subject {
  return &OrderCreateSubject{}
}
func (o *OrderCreateSubject) Register(observer Observer) {
  o.observerList = append(o.observerList, observer)
}
func (o *OrderCreateSubject) Notify(data interface{}) {
  for _, observer := range o.observerList {
    observer.Update(data)
  }
}


其中的 observerList 切片是用于存放所有订阅了下单事件的观察者。


接着便是编写观察者业务逻辑了,这里我实现了两个:


type B1CreateOrder struct {
}
func (b *B1CreateOrder) Update(data interface{}) {
  fmt.Printf("b1.....data %v \n", data)
}
type B2CreateOrder struct {
}
func (b *B2CreateOrder) Update(data interface{}) {
  fmt.Printf("b2.....data %v \n", data)
}


使用起来也非常简单:


func TestObserver(t *testing.T) {
  create := NewOrderCreate()
  create.Register(&B1CreateOrder{})
  create.Register(&B2CreateOrder{})
  create.Notify("abc123")
}


Output:


b1.....data abc123 
b2.....data abc123 


  1. 创建一个创建订单的主体 subject


  1. 注册所有的订阅事件。


  1. 在需要通知处调用 Notify 方法。


这样一旦我们需要修改各个事件的实现时就不会互相影响,即便是要加入其他实现也是非常容易的:


  1. 编写实现类。


  1. 注册进实体。


不会再修改核心流程。


配合容器


其实我们也可以省略掉注册事件的步骤,那就是使用容器;大致流程如下:


  1. 自定义的事件全部注入进容器。


  1. 再注册事件的地方从容器中取出所有的事件,挨个注册。


这里所使用的容器是 github.com/uber-go/dig


网络异常,图片无法展示
|


修改后的代码中,每当我们新增一个观察者(事件订阅)时,只需要使用容器所提供 Provide 函数注册进容器即可。


同时为了让容器能够支持同一个对象存在多个实例也需要新增部分代码:


Observer.go:


type Observer interface {
  Update(data interface{})
}
type (
  Instance struct {
    dig.Out
    Instance Observer `group:"observers"`
  }
  InstanceParams struct {
    dig.In
    Instances []Observer `group:"observers"`
  }
)


observer 接口中需要新增两个结构体用于存放同一个接口的多个实例。


group:"observers" 用于声明是同一个接口。


创建具体观察者对象时返回 Instance 对象。


func NewB1() Instance {
  return Instance{
    Instance: &B1CreateOrder{},
  }
}
func NewB2() Instance {
  return Instance{
    Instance: &B2CreateOrder{},
  }
}


其实就是用 Instance 包装了一次。


这样在注册观察者时,便能从 InstanceParams.Instances 中取出所有的观察者对象了。


err = c.Invoke(func(subject Subject, params InstanceParams) {
    for _, instance := range params.Instances {
      subject.Register(instance)
    }
  })


这样在使用时直接从容器中获取主题对象,然后通知即可:


err = c.Invoke(func(subject Subject) {
    subject.Notify("abc123")
  })


更多关于 dig 的用法可以参考官方文档:


pkg.go.dev/go.uber.org…


总结


有经验的开发者会发现和发布订阅模式非常类似,当然他们的思路是类似的;我们不用纠结与两者的差异(面试时除外);学会其中的思路更加重要。


相关文章
|
安全 调度 开发者
探索操作系统的心脏:现代内核架构与挑战
【10月更文挑战第7天】 本文深入探讨了现代操作系统内核的复杂性和功能性,从微观角度剖析了内核在系统运行中的核心作用及其面临的主要技术挑战。通过浅显易懂的语言解释专业概念,旨在为读者提供一个关于操作系统内核的全面视角。
169 2
|
Go
Golang生成随机数案例实战
关于如何使用Go语言生成随机数的三个案例教程。
340 91
Golang生成随机数案例实战
|
运维 负载均衡 监控
提升系统性能:高效运维的秘密武器——负载均衡技术
在当今数字化时代,系统的高可用性和高性能成为各类企业和组织追求的目标。本文旨在探讨负载均衡技术在运维工作中的关键作用,通过深入分析其原理、类型及实际应用案例,揭示如何利用这项技术优化资源分配,提高系统的响应速度和可靠性,确保用户体验的稳定与流畅。无论是面对突如其来的高流量冲击,还是日常的运维管理,负载均衡都展现出了不可或缺的重要性,成为现代IT架构中的基石之一。
627 4
|
前端开发 数据安全/隐私保护
【前端web入门第二天】03 表单-下拉菜单 文本域 label标签 按钮 【附注册信息综合案例】
本文档详细介绍了HTML表单的多种元素及其用法,包括下拉菜单(`<select>` 和 `<option>`)、文本域(`<textarea>`)、标签解释(`<label>`)、各类按钮(`<button>`)及表单重置功能、无语义布局标签(`<div>` 和 `<span>`)以及字符实体的应用。此外,还提供了一个完整的注册信息表单案例,涵盖个人信息、教育经历和工作经历等部分,展示了如何综合运用上述元素构建实用的表单。
245 6
【前端web入门第二天】03 表单-下拉菜单 文本域 label标签 按钮 【附注册信息综合案例】
|
监控
GIGE 协议摘录 —— GVCP 协议(二)(上)
GIGE 协议摘录 —— GVCP 协议(二)
793 2
|
SQL 分布式计算 关系型数据库
dump
【7月更文挑战第20天】
334 2
|
机器学习/深度学习 算法 开发工具
大语言模型的直接偏好优化(DPO)对齐在PAI-QuickStart实践
阿里云的人工智能平台PAI,作为一站式的机器学习和深度学习平台,对DPO算法提供了全面的技术支持。无论是开发者还是企业客户,都可以通过PAI-QuickStart轻松实现大语言模型的DPO对齐微调。本文以阿里云最近推出的开源大型语言模型Qwen2(通义千问2)系列为例,介绍如何在PAI-QuickStart实现Qwen2的DPO算法对齐微调。
|
Kubernetes Cloud Native jenkins
云原生时代:从Jenkins到Argo Workflows,构建高效CI Pipeline
基于Argo Workflows可以构建大规模、高效率、低成本的CI流水线
基于MATLAB的麻雀搜索算法SSA代码解释(对照论文公式)
基于MATLAB的麻雀搜索算法SSA代码解释(对照论文公式)