终于写完了创建型和结构型设计模式(共12个),现在开始写行为型设计模式(共11个)。观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子。
UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/18observer.go
1.定义
1.1观察者模式
观察者模式:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
UML:
1.2分析
观察者模式算是比较通用的设计模式了,思路也比较清晰。Subject里有Observer集合,当Subject有更新时,通知各个Observer。Subject为了管理Observer,自身设置了增加、删除功能。
当Subject有更新,通知Observer时,有很多细节值得讨论。
第一个问题是使用同步阻塞还是异步非阻塞
- 同步阻塞是最经典的实现方式,主要是为了代码解耦;
- 异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;
第二个问题是如何保证所有Observer都通知成功。
- 方案一是利用消息队列ACK的能力,Observer订阅消息队列。Subject只需要确保信息通知给消息队列即可。
- 方案二是Subject将失败的通知记录,方便后面进行重试。
- 方案三是定义好规范,例如只对网络失败这种错误进行记录,业务失败类型不管理,由业务自行保证成功。
第三个问题是不同进程/系统如何进行通知。
- 进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。
2.使用场景
观察者模式的使用场景还是很多的,它和享元模式一样,更多体现的是一种设计理念。
记得前些日子遇到一个场景,正好使用的是观察者模式。商家购买服务的订单,有多个状态,如交易成功、交易取消、开始履约等。业务场景只关注交易成功、交易取消和开始履约。如果不使用观察者模式,可以用if-else解决这个问题。但各个状态的处理逻辑比较复杂,而且状态有可能存在乱序的情况,导致要处理的case更多了。这时候用观察者模式就很合适,订单状态变更为Subject,具体处理逻辑为Observer,状态变更时,通知所有处理逻辑,谁合适处理便由谁处理。不但隔离性变好,扩展性也增强了。
3.代码实现
package main
import "fmt"
type PurchaseOperFunc func(status string, data string) (res bool, err error)
/**
* @Author: Jason Pang
* @Description: 注册的观察者
*/
var PurchaseOperFuncArr = []PurchaseOperFunc{
create,
isDeleted,
apply,
}
/**
* @Author: Jason Pang
* @Description: 用于创建的观察者
* @param status
* @param data
* @return res
* @return err
*/
func create(status string, data string) (res bool, err error) {
if status == "create" {
fmt.Println("开始创建")
return true, nil
}
return true, nil
}
/**
* @Author: Jason Pang
* @Description: 用于删除的观察者
* @param status
* @param data
* @return res
* @return err
*/
func isDeleted(status string, data string) (res bool, err error) {
if status == "delete" {
fmt.Println("开始删除")
return true, nil
}
return true, nil
}
/**
* @Author: Jason Pang
* @Description: 用于履约的观察者
* @param status
* @param data
* @return res
* @return err
*/
func apply(status string, data string) (res bool, err error) {
if status == "apply" {
fmt.Println("开始履约")
return true, nil
}
return true, nil
}
func main() {
status := "create"
data := "订单数据"
//有状态更新时,通知所有观察者
for _, oper := range PurchaseOperFuncArr {
res, err := oper(status, data)
if err != nil {
fmt.Println("操作失败")
break
}
if res == false {
fmt.Println("处理失败")
break
}
}
}
输出:
➜ myproject go run main.go
开始创建
这个代码是个简单版,但整体逻辑是一样的。这种写法的好处有如下几个:
- 如果要处理新的状态,则实现后再PurchaseOperFuncArr添加即可,改动不大
- 根据具体业务来说,只能有一个逻辑是真正负责执行的,所以有一个错误就报错,然后重试即可。不过具体逻辑需要做好幂等
总结
实际上,设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:https://shidawuhen.github.io/