设计模式之观察者模式

Go语言精选

共 5309字,需浏览 11分钟

 · 2020-09-05


观察者模式定义了对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。

观察者模式中有两个关键对象,分别是Subject(主题)和Observer(观察者)。它们之间是一种发布订阅关系,一(主题)对多(观察者)。

主题:管理主题数据,在数据更新时通知观察者(发布)。

观察者:订阅或注册主题,当主题数据发生更改,接受主题的发布消息。


应用案例


大多数人都有过网上抢购商品的经历,以淘宝的“淘抢购”为例。买家想要在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}


为衬衫(shirt)对象定义了三种状态status:TimeIsUp代表可以开抢了;IsEnd代表抢购结束;NotAtTime代表抢购还未开始。初始化衬衫对象时,默认状态为NotAtTimeshirtcustomers中存储的是订阅了主题(衬衫)的观察者们(想抢购衬衫的顾客们)。


  • 创建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



总结


松耦合


在观察者模式中,主题和观察者之间是松耦合的。

对于主题而言,它只知道观察者实现了Observer接口,主题不需要知道观察者的具体类是谁、做了什么或其他任何细节。因为主题唯一依赖的东西(即上文代码中shirtcustomers)是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。同样的,也可以在任何时候删除某些观察者。

在新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。


改变主题或观察者其中一方,并不会影响到另一方。只要他们之间的接口仍被遵守,我们就可以自由地改变他们。松耦合的设计能让我们建立富有弹性的OO系统。



潜在问题


1. 任何一种设计模式都不可能是完美无瑕的。松耦合的设计机制,将主题与观察者抽象在了不同的层次。但是这样的设计使得观察者模式没有相应的机制让观察者知道主题对象是怎么发生变化的,而仅仅只是知道主题发生了变化。


2. 另外,当一个主题对象有很多观察者时,将所有的观察者都通知到可能会花费很多时间。当主题状态更新频繁时,通知时间的消耗可能会导致观察者的更新时延。


3. 如果在观察者和主题之间有循环依赖的话,主题的状态变化会触发它们之间进行循环调用,可能导致系统崩溃。在使用观察者模式是要特别注意这一点。



应用场景举例


1.  普通粉丝关注微博大V,大V有动态更新时,粉丝的关注动态会提示通知。这里粉丝就是观察者,大V就是主题。粉丝可以选择关注或取消关注,大V的更新也只会推送给关注了的粉丝。


2. 订阅网红直播间,当主播开播,平台及时推送开播消息到订阅粉丝。


参考

1. 《Head First Design Patterns》

2. 《设计模式:可复用面向对象软件的基础






推荐阅读



学习交流 Go 语言,扫码回复「进群」即可


站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验


Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注


浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报