概念
观察者模式定义了一种'一对多'的得依赖关系, 让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时, 会通知所有观察者对象, 使它们能够根据根据变化做一些事情。
具体实现:
- 定义一个主题接口, 接口方法有:添加主题, 删除主题, 通知函数
- 定义一个观察者接口, 接口方法:OnNotify 被通知
- 实现主题接口, 主题接口保存一个观察者列表
- 实现不同的观察者, 观察者可以注册到主题
观察者模式和发布订阅模式的区别:
- 观察者模式有一个主题对象, 一个观察者对象。主题变化,主动通知观察者。
- 发布订阅模式:有3个对象, 发布者, 订阅者, 调度中心。 发布者发布一个主题消息, 调度中心将消息转发给订阅了这个主题的订阅者。
使用观察者模式的场景和优缺点
使用场景
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
事件多级触发场景。
跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
- 解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
- 在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,如果一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
代码实现
package main
import (
"fmt"
)
// Event 消息 1:吃饭
type Event struct {
Data int64
}
// 观察者接口
type Observer interface {
// 消息变动通知观察者
OnNotify(Event)
}
// 主题
type Subject interface {
// 添加主题
Attach(Observer)
// 删除主题
DeAttach(Observer)
// 通知消息
Notify(Event)
}
// 主题具体实现
type SubjectImpl struct {
// 用map来存放Observer
observers map[Observer]struct{}
}
// Attach ...
// 传入的l变量(ConcreteObserver1结构体变量)无论是指针还是值类型, 根据实现Observer结构体的接收器类型有关, 当然无论接收器类型是指针类型,值类型无所谓, map 匹配结构体本质上, 是判断结构体变量是否相等, 并不关心是否是源数据还是copy的数据.
func (o *SubjectImpl) Attach(l Observer) {
o.observers[l] = struct{}{}
}
// DeAttach ...
func (o *SubjectImpl) DeAttach(l Observer) {
delete(o.observers, l)
}
// Notify 把变化的消息发送给所有的观察者
func (o *SubjectImpl) Notify(e Event) {
for i := range o.observers {
i.OnNotify(e)
}
}
// 具体观察者1
type ConcreteObserver1 struct {
}
// OnNotify 收到消息变动
func (o *ConcreteObserver1) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是1号, 我要吃面包")
}
}
// 具体观察者2
type ConcreteObserver2 struct {
}
// OnNotify 收到消息变动
func (o *ConcreteObserver2) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是2号, 我要吃面条")
}
}
func main() {
// 初始化
n := SubjectImpl{
observers: map[Observer]struct{}{},
}
// 注册两个观察者
n.Attach(&ConcreteObserver1{})
n.Attach(&ConcreteObserver2{})
// 消息通知
n.Notify(Event{Data: 1})
}
订阅-发布模式
在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。
订阅发布模式和观察者模式的区别
- 订阅发布模式, 发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
- 观察者模式有两个重要的角色,即目标和观察者。在目标和观察者之间是没有事件通道的。一方面,观察者要想订阅目标事件,由于没有事件通道,因此必须将自己添加到目标(Subject) 中进行管理;另一方面,目标在触发事件的时候,也无法将通知操作(notify) 委托给事件通道,因此只能亲自去通知所有的观察者。
概念
观察者模式定义了一种'一对多'的得依赖关系, 让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时, 会通知所有观察者对象, 使它们能够根据根据变化做一些事情。
具体实现:
- 定义一个主题接口, 接口方法有:添加主题, 删除主题, 通知函数
- 定义一个观察者接口, 接口方法:OnNotify 被通知
- 实现主题接口, 主题接口保存一个观察者列表
- 实现不同的观察者, 观察者可以注册到主题
观察者模式和发布订阅模式的区别:
- 观察者模式有一个主题对象, 一个观察者对象。主题变化,主动通知观察者。
- 发布订阅模式:有3个对象, 发布者, 订阅者, 调度中心。 发布者发布一个主题消息, 调度中心将消息转发给订阅了这个主题的订阅者。
使用观察者模式的场景和优缺点
使用场景
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
事件多级触发场景。
跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
- 解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
- 在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,如果一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
代码实现
package main
import (
"fmt"
)
// Event 消息 1:吃饭
type Event struct {
Data int64
}
// 观察者接口
type Observer interface {
// 消息变动通知观察者
OnNotify(Event)
}
// 主题
type Subject interface {
// 添加主题
Attach(Observer)
// 删除主题
DeAttach(Observer)
// 通知消息
Notify(Event)
}
// 主题具体实现
type SubjectImpl struct {
// 用map来存放Observer
observers map[Observer]struct{}
}
// Attach ...
// 传入的l变量(ConcreteObserver1结构体变量)无论是指针还是值类型, 根据实现Observer结构体的接收器类型有关, 当然无论接收器类型是指针类型,值类型无所谓, map 匹配结构体本质上, 是判断结构体变量是否相等, 并不关心是否是源数据还是copy的数据.
func (o *SubjectImpl) Attach(l Observer) {
o.observers[l] = struct{}{}
}
// DeAttach ...
func (o *SubjectImpl) DeAttach(l Observer) {
delete(o.observers, l)
}
// Notify 把变化的消息发送给所有的观察者
func (o *SubjectImpl) Notify(e Event) {
for i := range o.observers {
i.OnNotify(e)
}
}
// 具体观察者1
type ConcreteObserver1 struct {
}
// OnNotify 收到消息变动
func (o *ConcreteObserver1) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是1号, 我要吃面包")
}
}
// 具体观察者2
type ConcreteObserver2 struct {
}
// OnNotify 收到消息变动
func (o *ConcreteObserver2) OnNotify(e Event) {
if e.Data == 1 {
fmt.Println("我是2号, 我要吃面条")
}
}
func main() {
// 初始化
n := SubjectImpl{
observers: map[Observer]struct{}{},
}
// 注册两个观察者
n.Attach(&ConcreteObserver1{})
n.Attach(&ConcreteObserver2{})
// 消息通知
n.Notify(Event{Data: 1})
}
订阅-发布模式
在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。
订阅发布模式和观察者模式的区别
- 订阅发布模式, 发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
- 观察者模式有两个重要的角色,即目标和观察者。在目标和观察者之间是没有事件通道的。一方面,观察者要想订阅目标事件,由于没有事件通道,因此必须将自己添加到目标(Subject) 中进行管理;另一方面,目标在触发事件的时候,也无法将通知操作(notify) 委托给事件通道,因此只能亲自去通知所有的观察者。
代码实现
package main
import "fmt"
type Event struct {
Message string
}
type ControlCenter interface {
Subscribe(topic string, f func(e *Event))
Unsubscribe()
Publish()
}
// 每个订阅者对应一个callback
type Subscribers map[string]func(e *Event)
type ControlCenterIml struct {
// 一个主题对应多个订阅者
TopicEvent map[string]Subscribers
}
func (c *ControlCenterIml) Subscribe(subcriberId, topic string, f func(e *Event)) {
if c.TopicEvent == nil {
return
}
if v, ok := c.TopicEvent[topic]; !ok {
subscribers := make(Subscribers)
subscribers[subcriberId] = f
c.TopicEvent[topic] = subscribers
} else {
v[subcriberId] = f
c.TopicEvent[topic] = v
}
}
func (c *ControlCenterIml) Unsubscribe(subcriberId, topic string) {
if c.TopicEvent == nil {
return
}
if subscribers, ok := c.TopicEvent[topic]; !ok {
return
} else {
delete(subscribers, subcriberId)
}
}
func (c *ControlCenterIml) Publish(topic string, e *Event) bool {
if c.TopicEvent == nil {
return false
}
var subscribers Subscribers
if v, ok := c.TopicEvent[topic]; !ok {
return false
} else {
subscribers = v
}
for k, v := range subscribers {
fmt.Println(k)
v(e)
}
return true
}
func main() {
subPub := ControlCenterIml{TopicEvent: make(map[string]Subscribers)}
subPub.Subscribe("123", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Subscribe("124", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Publish("study", &Event{Message: "hello lili"})
subPub.Unsubscribe("123", "study")
subPub.Publish("study", &Event{Message: "hello vivi"})
}
代码实现
package main
import "fmt"
type Event struct {
Message string
}
type ControlCenter interface {
Subscribe(topic string, f func(e *Event))
Unsubscribe()
Publish()
}
// 每个订阅者对应一个callback
type Subscribers map[string]func(e *Event)
type ControlCenterIml struct {
// 一个主题对应多个订阅者
TopicEvent map[string]Subscribers
}
func (c *ControlCenterIml) Subscribe(subcriberId, topic string, f func(e *Event)) {
if c.TopicEvent == nil {
return
}
if v, ok := c.TopicEvent[topic]; !ok {
subscribers := make(Subscribers)
subscribers[subcriberId] = f
c.TopicEvent[topic] = subscribers
} else {
v[subcriberId] = f
c.TopicEvent[topic] = v
}
}
func (c *ControlCenterIml) Unsubscribe(subcriberId, topic string) {
if c.TopicEvent == nil {
return
}
if subscribers, ok := c.TopicEvent[topic]; !ok {
return
} else {
delete(subscribers, subcriberId)
}
}
func (c *ControlCenterIml) Publish(topic string, e *Event) bool {
if c.TopicEvent == nil {
return false
}
var subscribers Subscribers
if v, ok := c.TopicEvent[topic]; !ok {
return false
} else {
subscribers = v
}
for k, v := range subscribers {
fmt.Println(k)
v(e)
}
return true
}
func main() {
subPub := ControlCenterIml{TopicEvent: make(map[string]Subscribers)}
subPub.Subscribe("123", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Subscribe("124", "study", func(e *Event) {
fmt.Println("recv something:", e.Message)
})
subPub.Publish("study", &Event{Message: "hello lili"})
subPub.Unsubscribe("123", "study")
subPub.Publish("study", &Event{Message: "hello vivi"})
}