本文转载自微信公众号“crossoverJie”,作者crossoverJie。转载本文请联系跨界姐公众号。前言无论是面试还是工作都会遇到设计模式,但是经常会遇到小伙伴抱怨设计模式在实际工作中应用的机会很小。刚好最近在工作中遇到使用观察者模式解决问题的场景,分享给大家。背景如下:在用户创建订单的标准流程中,需要做一些额外的事情:同时这些业务不是固定的,会根据业务发展随时增加和修改逻辑时间。如果逻辑直接写在点餐业务中,这个不是很核心业务的“集群”会占用越来越多,修改可能会影响正常的点餐流程。当然还有其他的方案,比如启动几个定时任务,定时扫描扫描订单,然后实现自己的业务逻辑;但是这样会浪费很多不必要的请求。观察者模式因此是观察者模式,当自身状态发生变化时由事件发布者通知,观察者获取消息实现业务逻辑。这样事件的发布者和接收者就可以完全解耦,互不影响;本质上也是开闭原则的一种实现。示例代码先大致了解一下观察者模式下使用的接口和关系:Subject接口:定义了注册实现和循环通知接口。观察者接口:定义接收主题通知的接口。主题和观察者接口都可以有多个实现。业务代码只需要使用Subject.Nofity()接口即可。下面我们来看一下创建订单过程中的实现案例。代码用go实现,其他语言类似。首先按照上图定义两个接口:typeSubjectinterface{Register(Observer)Notify(datainterface{})}typeObserverinterface{Update(datainterface{})}由于我们是订单事件,定义OrderCreateSubject来实现Subject:typeOrderCreateSubjectstruct{observerList[]Observer}funcNewOrderCreate()Subject{return&OrderCreateSubject{}}func(o*OrderCreateSubject)Register(observerObserver){o.observerList=append(o.observerList,observer)}func(o*OrderCreateSubject)Notify(数据接口{}){for_,observer:=rangeo.observerList{observer.Update(data)}}observerList切片用于存储所有订阅了订单事件的观察者。接下来是编写观察者业务逻辑,这里我实现了两个:typeB1CreateOrderstruct{}func(b*B1CreateOrder)Update(datainterface{}){fmt.Printf("b1.....data%v\n",data)}typeB2CreateOrderstruct{}func(b*B2CreateOrder)Update(datainterface{}){fmt.Printf("b2.....data%v\n",data)}使用起来也很简单:funcTestObserver(t*testing.T){create:=NewOrderCreate()create.Register(&B1CreateOrder{})create.Register(&B2CreateOrder{})create.Notify("abc123")}输出:b1.....dataabc123b2.....dataabc123创建一个创建订单的主题。注册所有订阅事件。在需要通知的地方调用Notify方法。这样,一旦我们需要修改每个事件的实现,就不会互相影响,而且很容易添加其他实现:编写实现类。注册到实体。核心流程不再修改。其实我们也可以省略向容器注册事件的步骤,即使用容器;大体流程如下:将所有自定义事件注入到容器中。再次注册事件的地方从容器中取出所有事件,一个一个注册。这里使用的容器是https://github.com/uber-go/dig的修改代码。每当我们添加观察者(事件订阅)时,只需要使用容器提供的Provide函数注册到容器中即可。就是这样。同时为了让容器支持同一个对象的多个实例,需要添加一些代码:typeObserverinterface{Update(datainterface{})}type(Instancestruct{dig.OutInstanceObserver`group:"observers"`}InstanceParamsstruct{dig.InInstances[]Observer`group:"observers"`})在观察者接口中,需要添加两个结构来存储同一个接口的多个实例。group:"observers"用于声明相同的接口。创建具体的观察者对象时会返回一个实例对象。funcNewB1()Instance{returnInstance{Instance:&B1CreateOrder{},}}funcNewB2()Instance{returnInstance{Instance:&B2CreateOrder{},}}其实就是用Instance包裹了一次。这样,在注册观察者时,可以从InstanceParams.Instances中获取所有的观察者对象。err=c.Invoke(func(subjectSubject,paramsInstanceParams){for_,instance:=rangeparams.Instances{subject.Register(instance)}})这样使用的时候可以直接从容器中获取subject对象,并且然后notify:err=c.Invoke(func(subjectSubject){subject.Notify("abc123")})dig的更多用法可以参考官方文档:https://pkg.go.dev/go.uber.org/dig#hdr-Value_Groups总结一下,有经验的开发者会发现它和发布-订阅模型非常相似,当然他们的想法是相似的;我们不需要担心两者之间的差异(面试时除外);学习思想更重要。
