设计模式之观察者模式

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

观察者模式中有两个关键对象,分别是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代表抢购还未开始。初始化衬衫对象时,默认状态为NotAtTime。shirt的customers中存储的是订阅了主题(衬衫)的观察者们(想抢购衬衫的顾客们)。

  • 创建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接口,主题不需要知道观察者的具体类是谁、做了什么或其他任何细节。因为主题唯一依赖的东西(即上文代码中shirt的customers)是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。同样的,也可以在任何时候删除某些观察者。

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

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

潜在问题

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

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

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

应用场景举例

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

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

参考

1. 《Head First Design Patterns》

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

往期精彩推荐

Golang技术分享

长按识别二维码关注我们

更多golang学习资料

回复关键词1024

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值