diff --git a/observer-pattern/README.md b/observer-pattern/README.md new file mode 100644 index 0000000..445f346 --- /dev/null +++ b/observer-pattern/README.md @@ -0,0 +1,247 @@ +# 观察者模式 + +## 写在前面 + +#### 定义 + +> wiki: 观察者模式是软体设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。 + +简单点说,可以想象成多个对象同时观察一个对象,当这个被观察的对象发生变化的时候,这些对象都会得到通知,可以做一些操作... + +#### 多啰嗦几句 + +在`PHP`世界中,最出名的观察者模式应该就是 `Laravel` 的事件了,`Laravel`是一个事件驱动的框架,所有的操作都通过事件进行解耦,实现了一个简单的观察者模式,比较典型的一个使用就是[数据库模型](https://laravel-china.org/articles/6657/model-events-and-observer-in-laravel),当观察到模型更改的时候,就会触发事件(`created`/`updated`/`deleted`...) + +最开始用模型观察者的时候,只要在 `Observers` 目录中创建一个观察者对象,并且添加观察者关联,就可以自动触发了,好神奇喔... + +观察者模式在实际开发中经常用到,主要存在于底层框架中,与业务逻辑解耦,业务逻辑只需要实现各种观察者被观察者即可。 + +## 类图 + +![](https://gss1.bdstatic.com/-vo3dSag_xI4khGkpoWK1HF6hhy/baike/s%3D500/sign=7d0c44b1d52a283447a6360b6bb4c92e/3801213fb80e7bec49b2574d2e2eb9389b506b35.jpg) + +(图源网络) + +## 角色 + + - 抽象观察者 + + - 具体观察者 + + - 抽象被观察者 + + - 具体被观察者 + + ## 举个栗子 + + 1. 创建抽象观察者 + + ``` + // 抽象观察者 + type IObserver interface { + Notify() // 当被观察对象有更改的时候,出发观察者的Notify() 方法 + } + ``` + + 2. 创建抽象被观察者 + + ```go + // 抽象被观察者 + type ISubject interface { + AddObservers(observers ...IObserver) // 添加观察者 + NotifyObservers() // 通知观察者 + } + ``` + + 3. 实现观察者 + ```go + + type Observer struct { + } + + func (o *Observer) Notify() { + fmt.Println("已经触发了观察者") + } + ``` + + 4. 实现被观察者 + + ```go + + type Subject struct { + observers []IObserver + } + + func (s *Subject) AddObservers(observers ...IObserver) { + s.observers = append(s.observers, observers...) + } + + func (s *Subject) NotifyObservers() { + for k := range s.observers { + s.observers[k].Notify() // 触发观察者 + } + } + ``` + + 5. 使用实例 + ```go + // 创建被观察者 + s := new(Subject) + // 创建观察者 + o := new(Observer) + // 为主题添加观察者 + s.AddObservers(o) + + // 这里的被观察者要做各种更改... + + // 更改完毕,触发观察者 + s.NotifyObservers() + ``` + +## 举个实际应用的例子 + + 对`PHP` 熟悉的同学可以看看这个,**Laravel中的[事件系统](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Event.md)和[观察者模式](https://github.com/kevinyan815/Learning_Laravel_Kernel/blob/master/articles/Observer.md)** + + 下面写一个我自己项目中的栗子: [github.com/silsuer/bingo-events](https://github.com/silsuer/bingo-events) + + 这是一个`golang`语言实现的事件系统,我正在试着把它应用到自己的[框架](https://github.com/silsuer/bingo)中,它实现了两种观察者模式,一种是实现了观察者接口的观察者模式,一种是使用了反射进行类型映射的观察者模式,下面分别来说一下... + + 1. 实现了观察者接口的方式: + + ```go + // 创建一个结构体,实现事件接口 + type listen struct { + bingo_events.Event + Name string + } + + func func main() { + // 事件对象 + app := bingo_events.NewApp() + l := new(listen) // 创建被观察者 + l.Name = "silsuer" + l.Attach(func (event interface{}, next func(event interface{})) { + // 由于监听器可监听的对象不一定非要实现 IEvent 接口,所以这里需要使用类型断言,将对象转换回原本的类型 + a := event.(*listen) + fmt.Println(a.Name) // output: silsuer + a.Name = "god" // 更改结构体的属性 + next(a) // 调用next函数继续走下一个监听器,如果此处不调用,程序将会终止在此处,不会继续往下执行 + }) + + // 触发事件 + app.Dispatch(l) + } + ``` + 这里我们使用结构体组合的形式实现了事件接口的实现,只要把`bingo-events.Event` 放入要监听的结构体中,就实现了`IEvent`接口,可以使用 `Attach()` 方法来添加观察者了, + + 这里的观察者是一个 `func (event interface{}, next func(event interface{})) ` 类型的方法, + + 第一个参数是触发的对象,毕竟观察者有时需要用到被观察者的属性,比如上面提到的`Laravel`的模型... + + 第二个参数是一个 `func(event interface{})` 类型的方法,实际上这里实现了一个 `pipeline`,来做拦截功能,可以实现观察者的截断,只有在观察者的最后调用了 `next()` 方法,事件才会继续向下一个观察者传递, + + `pipeline` 的原理我在 [参考Laravel制作基于golang的路由包](https://juejin.im/post/5bd498cbf265da0ac669986f) 中写过,用来做中间件拦截的操作。 + + + 2. 使用反射做观察者映射的方式 + + ```go + // 创建一个对象,不必实现事件接口 + type listen struct { + Name string + } + + func main() { + // 事件对象 + app := bingo_events.NewApp() + // 添加观察者 + app.Listen("*main.listen", ListenStruct) // 直接使用 Listen 方法,为监听的结构体添加一个回调 + l := new(listen) + l.Name = "silsuer" // 为对象属性赋值 + + // 复制完毕,开始分发事件,从上可知,共添加了两个观察者: ListenStruct 和 L2 + // 会按照监听的顺序执行,由于最开始已经添加了 ListenStruct 监听器,所以第二次再次添加的时候不会重复添加 + // 此处的分发,就是将参数顺序传入每一个监听器,进行后续操作 + app.Dispatch(l) + } + func ListenStruct(event interface{}, next func(event interface{})) { + // 由于监听器可监听的对象不一定非要实现 IEvent 接口,所以这里需要使用类型断言,将对象转换回原本的类型 + a := event.(*listen) + fmt.Println(a.Name) // output: silsuer + a.Name = "god" // 更改结构体的属性 + next(a) // 调用next函数继续走下一个监听器,如果此处不调用,程序将会终止在此处,不会继续往下执行 + } + ``` + + 这里我们没有使用实现了事件对象的 `Attach` 方法来添加观察者,而是使用一个字符串代表被观察者,这样就做到了无需实现观察者接口,就可以做到观察者模式。完整代码可以直接去看git仓库 + + 这里我们需要关注两个方法: `Listen()` 和 `Dispatch()` + + `bingo_events.App` 是一个服务容器,装载了所有事件和事件之间的映射关系 + + ```go + // 服务容器 + type App struct { + sync.RWMutex // 读写锁 + events map[string][]Listener // 事件映射 + } + ``` + + 下面看源代码: + + `Listen()`: + ```go + // 监听[事件][监听器],单独绑定一个监听器 + func (app *App) Listen(str string, listener Listener) { + app.Lock() + app.events[str] = append(app.events[str], listener) + app.Unlock() + } + ``` + + `app.events` 中保存的是通过字符串监听的对象,字符串就是通过`reflect.TypeOf(v).String()` 得到的字符串,例如上面的`listen`对象就是`*main.listen`,这是键,对应的值就是绑定的监听器方法 + + `Dispatch()` + + ```go + // 分发事件,传入各种事件,如果是 + func (app *App) Dispatch(events ...interface{}) { + // 容器分发数据 + var event string + for k := range events { + if _, ok := events[k].(string); ok { // 如果传入的是字符串类型的 + event = events[k].(string) + } else { + // 不是字符串类型的,那么得到其类型 + event = reflect.TypeOf(events[k]).String() + } + + // 如果实现了 事件接口 IEvent,则调用事件的观察者模式,得到所有的 + var observers []Listener + if _, ok := events[k].(IEvent); ok { + obs := events[k].(IEvent).Observers() + observers = append(observers, obs...) // 将事件中自行添加的观察者,放在所有观察者之后 + } + + // 如果存在map映射,则也放入观察者数组中 + if obs, exist := app.events[event]; exist { + observers = append(observers, obs...) + } + + if len(observers) > 0 { + // 得到了所有的观察者,这里通过pipeline来执行,通过next来控制什么时候调用这个观察者 + new(Pipeline).Send(events[k]).Through(observers).Then(func(context interface{}) { + + }) + } + + } + + } + ``` + `Dispatch()` 方法传入一个对象(或对象类型的字符串),是否实现事件接口都可以,遍历所有对象(如果传入的是字符串,就不做处理,如果是对象,通过反射提取对象的字符串名称),然后从当前 `App` 中的`map`提取对应的观察者,如果对象还实现了事件接口,则再获取挂载在这个事件上的所有观察者,将他们装在同一个 `slice` 中,构建成一个 `pipeline`,顺序执行观察者。 + +> 上述代码均放在 [golang-design-patterns](https://github.com/silsuer/golang-design-patterns) 这个仓库中 + +> 打个广告,推荐一下自己写的 go web框架 [bingo](https://github.com/silsuer/bingo),求star,求PR ~ + \ No newline at end of file diff --git a/observer-pattern/main.go b/observer-pattern/main.go new file mode 100644 index 0000000..c1896ee --- /dev/null +++ b/observer-pattern/main.go @@ -0,0 +1,51 @@ +package main + +import "fmt" + +// 抽象观察者 +type IObserver interface { + Notify() // 当被观察对象有更改的时候,出发观察者的Notify() 方法 +} + +// 抽象被观察者 +type ISubject interface { + AddObservers(observers ...IObserver) // 添加观察者 + NotifyObservers() // 通知观察者 +} + +type Observer struct { +} + +func (o *Observer) Notify() { + fmt.Println("已经触发了观察者") +} + +type Subject struct { + observers []IObserver +} + +func (s *Subject) AddObservers(observers ...IObserver) { + //fmt.Println(1) + s.observers = append(s.observers, observers...) + //fmt.Println(s.observers) +} + +func (s *Subject) NotifyObservers() { + for k := range s.observers { + s.observers[k].Notify() // 触发观察者 + } +} + +func main() { + // 创建被观察者 + s := new(Subject) + // 创建观察者 + o := new(Observer) + // 为主题添加观察者 + s.AddObservers(o) + + // 这里的被观察者要做各种更改... + + // 更改完毕,触发观察者 + s.NotifyObservers() +}