设计模式之观察者模式
应用案例
大多数人都有过网上抢购商品的经历,以淘宝的“淘抢购”为例。买家想要在22点抢购衬衫,点击“提醒我”按钮。那么淘宝就会在开抢之前,及时把抢购消息推送给买家。
该消息提醒推送的实现,就是经典的观察者模式。
代码实现
UML类图
创建Subject接口
1package observer
2
3type Subject interface {
4 Register(o Observer)
5 Deregister(o Observer) error
6 NotifyObservers()
7}
创建Observer接口
1package observer
2
3type Observer interface {
4 Update(name, status string)
5 GetID() int
6}
创建shirt对象,实现Subjuect接口
1package observer
2
3import (
4 "fmt"
5 "sync"
6)
7
8const (
9 TimeIsUp = "time is up"
10 IsEnd = "is end"
11 NotAtTime = "not at time"
12)
13
14type shirt struct {
15 sync.Mutex
16 customers []Observer
17 status string
18 name string
19}
20
21//NewShirt create a shirt, its default status is not at time.
22func NewShirt() *shirt {
23 return &shirt{status: NotAtTime, name: "shirt"}
24}
25
26// Register registers the observer (customer) of this shirt.
27func (s *shirt) Register(o Observer) {
28 s.Lock()
29 defer s.Unlock()
30 s.customers = append(s.customers, o)
31 fmt.Printf("[%s] registered a new customer with ID[%d]\n", s.name, o.GetID())
32}
33
34// Deregister removes the observer (customer) from the customers list.
35// The removed observer (customer) won't get notifications any more.
36func (s *shirt) Deregister(o Observer) error {
37 s.Lock()
38 defer s.Unlock()
39 var index int
40 var found bool
41 id := o.GetID()
42 for i, c := range s.customers {
43 if c.GetID() == id {
44 index = i
45 found = true
46 break
47 }
48 }
49 if !found {
50 return fmt.Errorf("Customer %d not found\n", id)
51 }
52 s.customers = append(s.customers[:index], s.customers[index+1:]...)
53 fmt.Printf("Removed the customer with ID[%d]\n", id)
54 return nil
55}
56
57// NotifyObservers notifies the customers (customers) when the shirt has in status "time is up".
58func (s *shirt) NotifyObservers() {
59 s.Lock()
60 defer s.Unlock()
61 wg := sync.WaitGroup{}
62 for _, c := range s.customers {
63 wg.Add(1)
64 go func(c Observer) {
65 defer wg.Done()
66 c.Update(s.name, s.status)
67 }(c)
68 }
69 wg.Wait()
70 fmt.Println("Finished notify customers")
71}
创建customer对象,实现Observer接口
1package observer
2
3import "fmt"
4
5type customer struct {
6 ID int
7 wantItemStatus string
8}
9
10// NewCustomer creates a new customer with an ID
11func NewCustomers(id int) *customer {
12 return &customer{ID: id}
13}
14
15// Update function updates the item status of the customer's want.
16func (c *customer) Update(name, status string) {
17 c.wantItemStatus = status
18 fmt.Printf("Update: hi customer %d, the item[%s] you want is [%v] now\n", c.ID, name, c.wantItemStatus)
19}
20
21// GetID returns the ID of the customer.
22func (c customer) GetID() int {
23 return c.ID
24}
整合测试
1package observer
2
3import (
4 "fmt"
5 "testing"
6)
7
8// UpdateShirtStatusForTest updates the status of a shirt.
9// This function is for testing only.
10func (s *shirt) UpdateShirtStatusForTest(status string) {
11 if s.status != status {
12 fmt.Printf("Update status of the [shirt]: previous is [%s], current is [%s]\n", s.status, status)
13 s.status = status
14 }
15}
16func TestRushToBuy(t *testing.T) {
17 c1 := NewCustomers(1)
18 c2 := NewCustomers(2)
19 c3 := NewCustomers(3)
20
21 s := NewShirt()
22 s.Register(c1)
23 s.Register(c2)
24 s.Register(c3)
25
26 s.UpdateShirtStatusForTest(TimeIsUp)
27 s.NotifyObservers()
28}
测试结果
1=== RUN TestRushToBuy
2[shirt] registered a new customer with ID[1]
3[shirt] registered a new customer with ID[2]
4[shirt] registered a new customer with ID[3]
5Update status of the [shirt]: previous is [not at time], current is [time is up]
6Update: hi customer 1, the item[shirt] you want is [time is up] now
7Update: hi customer 2, the item[shirt] you want is [time is up] now
8Update: hi customer 3, the item[shirt] you want is [time is up] now
9Finished notify customers
10--- PASS: TestRushToBuy (0.00s)
11PASS
总结
改变主题或观察者其中一方,并不会影响到另一方。只要他们之间的接口仍被遵守,我们就可以自由地改变他们。松耦合的设计能让我们建立富有弹性的OO系统。
1. 任何一种设计模式都不可能是完美无瑕的。松耦合的设计机制,将主题与观察者抽象在了不同的层次。但是这样的设计使得观察者模式没有相应的机制让观察者知道主题对象是怎么发生变化的,而仅仅只是知道主题发生了变化。
2. 另外,当一个主题对象有很多观察者时,将所有的观察者都通知到可能会花费很多时间。当主题状态更新频繁时,通知时间的消耗可能会导致观察者的更新时延。
3. 如果在观察者和主题之间有循环依赖的话,主题的状态变化会触发它们之间进行循环调用,可能导致系统崩溃。在使用观察者模式是要特别注意这一点。
应用场景举例
1. 普通粉丝关注微博大V,大V有动态更新时,粉丝的关注动态会提示通知。这里粉丝就是观察者,大V就是主题。粉丝可以选择关注或取消关注,大V的更新也只会推送给关注了的粉丝。
2. 订阅网红直播间,当主播开播,平台及时推送开播消息到订阅粉丝。
参考
1. 《Head First Design Patterns》
2. 《设计模式:可复用面向对象软件的基础》
推荐阅读
站长 polarisxu
自己的原创文章
不限于 Go 技术
职场和创业经验
Go语言中文网
每天为你
分享 Go 知识
Go爱好者值得关注
评论