一、什么是观察者模式?
观察者模式又称为监听模式、事件订阅者模式。其定义如下:
在对象间定义一对多的依赖关系,当这个对象状态发生改变时所有依赖它的对象都会被通知并自动更新。
观察者模式是一种一对多的关系,可以有任意个(1个或多个)观察者对象同时监听(观察)某一个对象。
二、模型说明
1.设计要点
设计观察者模式的程序时要注意以下几点。
(1)要明确谁是观察者谁是被观察者。一般观察者与被观察者之间是多对一的关系,一个被观察者对象可以有多个观察者对象。
(2)被观察者对象在发送广播通知时,无须指定具体的观察者对象,观察者对象可自己决定是否订阅被观察者对象的通知。
(3)被观察者至少有三个方法:添加观察者、移除观察者、通知观察者的方法。观察者至少有一个方法:更新方法,即更新当前的内容,做出相应的处理。
2.推模型和拉模型
观察者模式根据其侧重的功能可分为推模型和拉模型。
(1)推模型
被观察者对象向观察者对象推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。一般在这种模型的实现中会将被观察者对象中的全部或部分信息通过作为观察者的更新方法的参数传递给观察者。
(2)拉模型
被观察者对象在通知观察者时,只传递少量信息。如果观察者需要更详细的信息,由观察者主动到被观察者对象中获取,相当于观察者从被观察者对象中拉取数据。
三、观察者模式的优缺点
1.优点
- 开闭原则。 你无需修改被观察者代码就能引入新的观察者。
- 你可以在运行时建立对象之间的联系。
2.缺点
- 观察者的通知顺序是随机的。
四、应用场景
- 对一个对象状态或数据的更新需要其他对象同步更新,或者一个对象的更新需要依赖另一个对象的更新。
- 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送等。
五、代码实现
Code
package observer
import "fmt"
//被观察者接口
type IGoods interface {
Register(person IPerson)
Remove(person IPerson)
Notify()
}
type Goods struct {
Name string
InStock bool
persons []IPerson
}
func NewGoods(name string) *Goods {
return &Goods{
Name: name,
}
}
func (g *Goods) updateAvailability() {
fmt.Printf("Goods %s is now in stock\n", g.Name)
g.InStock = true
//通知观察者
g.Notify()
}
func (g *Goods) removePerson(persons []IPerson, person IPerson) []IPerson {
l := len(persons)
for i, per := range persons {
if per.getId() == person.getId() {
persons[l-1], persons[i] = persons[i], persons[l-1]
return persons[:l-1]
}
}
return persons
}
// 实现Register()方法,添加观察者
func (g *Goods) Register(person IPerson) {
g.persons = append(g.persons, person)
}
//实现Remove()方法,移除观察者
func (g *Goods) Remove(person IPerson) {
g.persons = g.removePerson(g.persons, person)
}
// 通知观察者
func (g *Goods) Notify() {
for _, person := range g.persons {
person.Update(g.Name)
}
}
//观察者接口
type IPerson interface {
Update(msg string)
getId() string
}
type Person struct {
Id string
}
func (p *Person) Update(pName string) {
fmt.Printf("Sending email to person %s for Goods %s\n", p.Id, pName)
}
func (p *Person) getId() string {
return p.Id
}
复制代码
单元测试
package observer
import "testing"
func TestGoods(t *testing.T) {
book := NewGoods("book")
observer1 := &Person{
Id: "observ1@qq.com",
}
observer2 := &Person{
Id: "observ2@qq.com",
}
book.Register(observer1)
book.Register(observer2)
book.updateAvailability()
}
复制代码
output
=== RUN TestGoods
Goods book is now in stock
Sending email to person observ1@qq.com for Goods book
Sending email to person observ2@qq.com for Goods book
— PASS: TestGoods (0.00s)
PASS