缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

观察者模式

  1. 观察者模式(Observer Pattern)又叫作发布-订阅(Publish/Subscribe)模式、
  2. 模型-视图(Model/View)模式、
  3. 源-监听器(Source/Listener)模式,
  4. 或从属者(Dependent)模式。
  5. 定义一种一对多的依赖关系,
  6. 一个主题对象可被多个观察者对象同时监听,
  7. 使得每当主题对象状态变化时,
  8. 所有依赖它的对象都会得到通知并被自动更新,
  9. 属于行为型设计模式。
  10. (摘自 谭勇德 <<设计模式就该这样学>>)

场景

  • 某智能app, 需添加自定义闹铃的功能
  • 闹铃可设定时间, 以及是否每日重复
  • 可设定多个闹铃
  • 根据观察者模式, 每个闹铃对象, 都是时间服务的观察者, 监听时间变化的事件.

设计

  • ITimeService: 定义时间服务的接口, 接受观察者的注册和注销
  • ITimeObserver: 定义时间观察者接口, 接收时间变化事件的通知
  • tMockTimeService: 虚拟的时间服务, 自定义时间倍率以方便时钟相关的测试
  • AlarmClock: 闹铃的实现类, 实现ITimeObserver接口以订阅时间变化通知

单元测试

observer_pattern_test.go, 定义了一个临时会议的一次性闹铃, 以及一系列日常作息的重复闹铃.

  1. package behavioral_patterns
  2. import (
  3. "learning/gooop/behavioral_patterns/observer"
  4. "testing"
  5. "time"
  6. )
  7. func Test_ObserverPattern(t *testing.T) {
  8. _ = observer.NewAlarmClock("下午开会", 14,30, false)
  9. _ = observer.NewAlarmClock("起床", 6,0, true)
  10. _ = observer.NewAlarmClock("午饭", 12,30, true)
  11. _ = observer.NewAlarmClock("午休", 13,0, true)
  12. _ = observer.NewAlarmClock("晚饭", 18,30, true)
  13. clock := observer.NewAlarmClock("晚安", 22,0, true)
  14. for {
  15. if clock.Occurs() >= 2 {
  16. break
  17. }
  18. time.Sleep(time.Second)
  19. }
  20. }

测试输出

  1. $ go test -v observer_pattern_test.go
  2. === RUN Test_ObserverPattern
  3. 下午开会.next = 2021-02-11 14:30:00
  4. 起床.next = 2021-02-12 06:00:00
  5. 午饭.next = 2021-02-11 12:30:00
  6. 午休.next = 2021-02-11 13:00:00
  7. 晚饭.next = 2021-02-11 18:30:00
  8. 晚安.next = 2021-02-11 22:00:00
  9. 2021-02-11 11:51:05 时间=2021-02-11 12:30:04 闹铃 午饭
  10. 2021-02-11 11:51:06 时间=2021-02-11 13:00:04 闹铃 午休
  11. 2021-02-11 11:51:09 时间=2021-02-11 14:30:04 闹铃 下午开会
  12. 2021-02-11 11:51:17 时间=2021-02-11 18:30:04 闹铃 晚饭
  13. 2021-02-11 11:51:24 时间=2021-02-11 22:00:04 闹铃 晚安
  14. 2021-02-11 11:51:40 时间=2021-02-12 06:00:04 闹铃 起床
  15. 2021-02-11 11:51:53 时间=2021-02-12 12:30:04 闹铃 午饭
  16. 2021-02-11 11:51:54 时间=2021-02-12 13:00:04 闹铃 午休
  17. 2021-02-11 11:52:05 时间=2021-02-12 18:30:04 闹铃 晚饭
  18. 2021-02-11 11:52:12 时间=2021-02-12 22:00:04 闹铃 晚安
  19. --- PASS: Test_ObserverPattern (69.01s)
  20. PASS
  21. ok command-line-arguments 69.012s

ITimeService.go

定义时间服务的接口, 接受观察者的注册和注销

  1. package observer
  2. type ITimeService interface {
  3. Attach(observer ITimeObserver)
  4. Detach(id string)
  5. }

ITimeObserver.go

定义时间观察者接口, 接收时间变化事件的通知

  1. package observer
  2. import "time"
  3. type ITimeObserver interface {
  4. ID() string
  5. TimeElapsed(now *time.Time)
  6. }

tMockTimeService.go

虚拟的时间服务, 自定义时间倍率以方便时钟相关的测试

  1. package observer
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. "time"
  6. )
  7. type tMockTimeService struct {
  8. observers map[string]ITimeObserver
  9. rwmutex *sync.RWMutex
  10. speed int64
  11. state int64
  12. }
  13. func NewMockTimeService(speed int64) ITimeService {
  14. it := &tMockTimeService{
  15. observers: make(map[string]ITimeObserver, 0),
  16. rwmutex: new(sync.RWMutex),
  17. speed: speed,
  18. state: 0,
  19. }
  20. it.Start()
  21. return it
  22. }
  23. func (me *tMockTimeService) Start() {
  24. if !atomic.CompareAndSwapInt64(&(me.state), 0, 1) {
  25. return
  26. }
  27. go func() {
  28. timeFrom := time.Now()
  29. timeOffset := timeFrom.UnixNano()
  30. for range time.Tick(time.Duration(100)*time.Millisecond) {
  31. if me.state == 0 {
  32. break
  33. }
  34. nanos := (time.Now().UnixNano() - timeOffset) * me.speed
  35. t := timeFrom.Add(time.Duration(nanos) * time.Nanosecond)
  36. me.NotifyAll(&t)
  37. }
  38. }()
  39. }
  40. func (me *tMockTimeService) NotifyAll(now *time.Time) {
  41. me.rwmutex.RLock()
  42. defer me.rwmutex.RUnlock()
  43. for _,it := range me.observers {
  44. go it.TimeElapsed(now)
  45. }
  46. }
  47. func (me *tMockTimeService) Attach(it ITimeObserver) {
  48. me.rwmutex.Lock()
  49. defer me.rwmutex.Unlock()
  50. me.observers[it.ID()] = it
  51. }
  52. func (me *tMockTimeService) Detach(id string) {
  53. me.rwmutex.Lock()
  54. defer me.rwmutex.Unlock()
  55. delete(me.observers, id)
  56. }
  57. var GlobalTimeService = NewMockTimeService(1800)

AlarmClock.go

闹铃的实现类, 实现ITimeObserver接口以订阅时间变化通知

  1. package observer
  2. import (
  3. "fmt"
  4. "sync/atomic"
  5. "time"
  6. )
  7. type AlarmClock struct {
  8. id string
  9. name string
  10. hour time.Duration
  11. minute time.Duration
  12. repeatable bool
  13. next *time.Time
  14. occurs int
  15. }
  16. var gClockID int64 = 0
  17. func newClockID() string {
  18. id := atomic.AddInt64(&gClockID, 1)
  19. return fmt.Sprintf("AlarmClock-%d", id)
  20. }
  21. func NewAlarmClock(name string, hour int, minute int, repeatable bool) *AlarmClock {
  22. it := &AlarmClock{
  23. id: newClockID(),
  24. name: name,
  25. hour: time.Duration(hour),
  26. minute: time.Duration(minute),
  27. repeatable: repeatable,
  28. next: nil,
  29. occurs: 0,
  30. }
  31. it.next = it.NextAlarmTime()
  32. GlobalTimeService.Attach(it)
  33. return it
  34. }
  35. func (me *AlarmClock) NextAlarmTime() *time.Time {
  36. now := time.Now()
  37. today, _ := time.ParseInLocation("2006-01-02 15:04:05", fmt.Sprintf("%s 00:00:00", now.Format("2006-01-02")), time.Local)
  38. t := today.Add(me.hour *time.Hour).Add(me.minute * time.Minute)
  39. if t.Unix() < now.Unix() {
  40. t = t.Add(24*time.Hour)
  41. }
  42. fmt.Printf("%s.next = %s\n", me.name, t.Format("2006-01-02 15:04:05"))
  43. return &t
  44. }
  45. func (me *AlarmClock) ID() string {
  46. return me.name
  47. }
  48. func (me *AlarmClock) TimeElapsed(now *time.Time) {
  49. it := me.next
  50. if it == nil {
  51. return
  52. }
  53. if now.Unix() >= it.Unix() {
  54. me.occurs++
  55. fmt.Printf("%s 时间=%s 闹铃 %s\n", time.Now().Format("2006-01-02 15:04:05"), now.Format("2006-01-02 15:04:05"), me.name)
  56. if me.repeatable {
  57. t := me.next.Add(24*time.Hour)
  58. me.next = &t
  59. } else {
  60. GlobalTimeService.Detach(me.ID())
  61. }
  62. }
  63. }
  64. func (me *AlarmClock) Occurs() int {
  65. return me.occurs
  66. }

观察者模式小结

  1. 观察者模式的优点
  2. 1)观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则。
  3. 2)分离了表示层(观察者)和数据逻辑层(被观察者),
  4. 并且建立了一套触发机制,使得数据的变化可以响应到多个表示层上。
  5. 3)实现了一对多的通信机制,支持事件注册机制,支持兴趣分发机制,
  6. 当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。
  7. 观察者模式的缺点
  8. 1)如果观察者数量过多,则事件通知会耗时较长。
  9. 2)事件通知呈线性关系,如果其中一个观察者处理事件卡壳,则会影响后续的观察者接收该事件。
  10. 3)如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。
  11. (摘自 谭勇德 <<设计模式就该这样学>>)

(end)