观察者模式#
观察者模式是一种行为设计模式,允许你定义一种「订阅机制」,可以在目标对象事件发生时通知多个「观察」该对象的其他对象。
主要思想#
在观察者模式中主要存在着两个重要角色:「发布者」(publisher)以及「订阅者」(subscriber)。
- 发布者:拥有一些值得关注的状态的对象,同时他要将自身的状态改变通知给其他对象。
- 订阅者:所有希望关注到发布者状态变化的其他对象。 观察者模式建议我们为发布者类添加订阅机制,让每个对象都能够订阅或者取消订阅发布者事件流。
从代码实践来看,简单的订阅机制在发布者类设计中体现在以下两个方面:
- 用于存储订阅者对象引用的列表成员变量
- 用于添加或者删除该列表中订阅得公有方法 当发布者类状态改变时,则遍历其存储的订阅者列表,并调用每个订阅者特定的通知方法。
当然,实际情况中都会存在着十几个不同的订阅者类,以及是十几个不同的发布者,所以我们应该坚定我们的「面向接口编程」。
所有的订阅者都必须实现同样的接口,发布者只能通过该接口和订阅者交互,接口中必须声明通知方法和参数。所有发布者都应该实现同样的接口,该接口就仅仅需要描述几个订阅方法即可。 这样订阅者就能通过不与具体发布者类耦合的情况下观察发布者的状态。
具体结构#
代码示例#
场景如下:在电商网站中,客户会对缺货的特定商品表现出兴趣。根绝观察者模式,客户应只订阅其感兴趣的特定商品,商品可用时便会收到到货通知。特定的用户收到特定的商品,而不是商家向所有客户发布通知,也不是商家只向一个客户发布通知。
type Subscriber interface {
Notify(msg string)
GetId() string
}
type Publisher interface {
Register(sub Subscriber)
Deregister(sub Subscriber)
NotifyAll()
}
type Customer struct {
Id string
}
func (c *Customer) Notify(msg string) {
fmt.Println("Get the new subscribes: ", msg)
}
func (c *Customer) GetId() string {
return c.Id
}
type Merchant struct {
subs []Subscriber
}
func (m *Merchant) Register(sub Subscriber) {
m.subs = append(m.subs, sub)
}
func (m *Merchant) NotifyAll() {
for _, s := range m.subs {
s.Notify("New shirt!")
}
}
func (m *Merchant) DeRegister(sub Subscriber) []Subscriber {
for i, s := range m.subs {
if s.GetId() == sub.GetId() {
m.subs[i], m.subs[len(m.subs)-1] = m.subs[len(m.subs)-1], m.subs[i]
return m.subs[:len(m.subs)-1]
}
}
return m.subs
}
func TestObserver(t *testing.T) {
shirtItem := Merchant{}
observerFirst := &Customer{Id: "abc@gmail.com"}
observerSecond := &Customer{Id: "xyz@gmail.com"}
shirtItem.Register(observerFirst)
shirtItem.Register(observerSecond)
shirtItem.DeRegister(observerFirst)
shirtItem.NotifyAll()
fmt.Printf("%#v", len(shirtItem.subs))
}
go总结#
- 当一个对象状态的改变需要改变其他的对象,或者实际对象是实际未知的或动态变化的时候,可以使用观察者模式。
- 订阅列表是动态的,因此订阅者可随时加入或离开该列表
How To Implement#
- 仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类。
- 声明订阅者接口。 该接口至少应声明一个 update方法。
- 声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互。
- 确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。 但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。
- 创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。
- 在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。 还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。
- 客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。