手撸golang 行为型设计模式 观察者模式
2010 年 8 月 18 日
手撸golang 行为型设计模式 观察者模式
缘起
最近复习设计模式
拜读谭勇德的<>
本系列笔记拟采用golang练习之
观察者模式
观察者模式(Observer Pattern)又叫作发布-订阅(Publish/Subscribe)模式、 模型-视图(Model/View)模式、 源-监听器(Source/Listener)模式, 或从属者(Dependent)模式。 定义一种一对多的依赖关系, 一个主题对象可被多个观察者对象同时监听, 使得每当主题对象状态变化时, 所有依赖它的对象都会得到通知并被自动更新, 属于行为型设计模式。 (摘自 谭勇德 <>)
场景
- 某智能app, 需添加自定义闹铃的功能
- 闹铃可设定时间, 以及是否每日重复
- 可设定多个闹铃
- 根据 观察者模式 , 每个闹铃对象, 都是时间服务的观察者, 监听时间变化的事件.
设计
- ITimeService: 定义时间服务的接口, 接受观察者的注册和注销
- ITimeObserver: 定义时间观察者接口, 接收时间变化事件的通知
- tMockTimeService: 虚拟的时间服务, 自定义时间倍率以方便时钟相关的测试
- AlarmClock: 闹铃的实现类, 实现ITimeObserver接口以订阅时间变化通知
单元测试
observer_pattern_test.go, 定义了一个临时会议的一次性闹铃, 以及一系列日常作息的重复闹铃.
package behavioral_patterns import ( "learning/gooop/behavioral_patterns/observer" "testing" "time" ) func Test_ObserverPattern(t *testing.T) { _ = observer.NewAlarmClock("下午开会", 14,30, false) _ = observer.NewAlarmClock("起床", 6,0, true) _ = observer.NewAlarmClock("午饭", 12,30, true) _ = observer.NewAlarmClock("午休", 13,0, true) _ = observer.NewAlarmClock("晚饭", 18,30, true) clock := observer.NewAlarmClock("晚安", 22,0, true) for { if clock.Occurs() >= 2 { break } time.Sleep(time.Second) } }
测试输出
$ go test -v observer_pattern_test.go === RUN Test_ObserverPattern 下午开会.next = 2021-02-11 14:30:00 起床.next = 2021-02-12 06:00:00 午饭.next = 2021-02-11 12:30:00 午休.next = 2021-02-11 13:00:00 晚饭.next = 2021-02-11 18:30:00 晚安.next = 2021-02-11 22:00:00 2021-02-11 11:51:05 时间=2021-02-11 12:30:04 闹铃 午饭 2021-02-11 11:51:06 时间=2021-02-11 13:00:04 闹铃 午休 2021-02-11 11:51:09 时间=2021-02-11 14:30:04 闹铃 下午开会 2021-02-11 11:51:17 时间=2021-02-11 18:30:04 闹铃 晚饭 2021-02-11 11:51:24 时间=2021-02-11 22:00:04 闹铃 晚安 2021-02-11 11:51:40 时间=2021-02-12 06:00:04 闹铃 起床 2021-02-11 11:51:53 时间=2021-02-12 12:30:04 闹铃 午饭 2021-02-11 11:51:54 时间=2021-02-12 13:00:04 闹铃 午休 2021-02-11 11:52:05 时间=2021-02-12 18:30:04 闹铃 晚饭 2021-02-11 11:52:12 时间=2021-02-12 22:00:04 闹铃 晚安 --- PASS: Test_ObserverPattern (69.01s) PASS ok command-line-arguments 69.012s
ITimeService.go
定义时间服务的接口, 接受观察者的注册和注销
package observer type ITimeService interface { Attach(observer ITimeObserver) Detach(id string) }
ITimeObserver.go
定义时间观察者接口, 接收时间变化事件的通知
package observer import "time" type ITimeObserver interface { ID() string TimeElapsed(now *time.Time) }
tMockTimeService.go
虚拟的时间服务, 自定义时间倍率以方便时钟相关的测试
package observer import ( "sync" "sync/atomic" "time" ) type tMockTimeService struct { observers map[string]ITimeObserver rwmutex *sync.RWMutex speed int64 state int64 } func NewMockTimeService(speed int64) ITimeService { it := &tMockTimeService{ observers: make(map[string]ITimeObserver, 0), rwmutex: new(sync.RWMutex), speed: speed, state: 0, } it.Start() return it } func (me *tMockTimeService) Start() { if !atomic.CompareAndSwapInt64(&(me.state), 0, 1) { return } go func() { timeFrom := time.Now() timeOffset := timeFrom.UnixNano() for range time.Tick(time.Duration(100)*time.Millisecond) { if me.state == 0 { break } nanos := (time.Now().UnixNano() - timeOffset) * me.speed t := timeFrom.Add(time.Duration(nanos) * time.Nanosecond) me.NotifyAll(&t) } }() } func (me *tMockTimeService) NotifyAll(now *time.Time) { me.rwmutex.RLock() defer me.rwmutex.RUnlock() for _,it := range me.observers { go it.TimeElapsed(now) } } func (me *tMockTimeService) Attach(it ITimeObserver) { me.rwmutex.Lock() defer me.rwmutex.Unlock() me.observers[it.ID()] = it } func (me *tMockTimeService) Detach(id string) { me.rwmutex.Lock() defer me.rwmutex.Unlock() delete(me.observers, id) } var GlobalTimeService = NewMockTimeService(1800)
AlarmClock.go
闹铃的实现类, 实现ITimeObserver接口以订阅时间变化通知
package observer import ( "fmt" "sync/atomic" "time" ) type AlarmClock struct { id string name string hour time.Duration minute time.Duration repeatable bool next *time.Time occurs int } var gClockID int64 = 0 func newClockID() string { id := atomic.AddInt64(&gClockID, 1) return fmt.Sprintf("AlarmClock-%d", id) } func NewAlarmClock(name string, hour int, minute int, repeatable bool) *AlarmClock { it := &AlarmClock{ id: newClockID(), name: name, hour: time.Duration(hour), minute: time.Duration(minute), repeatable: repeatable, next: nil, occurs: 0, } it.next = it.NextAlarmTime() GlobalTimeService.Attach(it) return it } func (me *AlarmClock) NextAlarmTime() *time.Time { now := time.Now() today, _ := time.ParseInLocation("2006-01-02 15:04:05", fmt.Sprintf("%s 00:00:00", now.Format("2006-01-02")), time.Local) t := today.Add(me.hour *time.Hour).Add(me.minute * time.Minute) if t.Unix() = it.Unix() { me.occurs++ 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) if me.repeatable { t := me.next.Add(24*time.Hour) me.next = &t } else { GlobalTimeService.Detach(me.ID()) } } } func (me *AlarmClock) Occurs() int { return me.occurs }
观察者模式小结
观察者模式的优点 (1)观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则。 (2)分离了表示层(观察者)和数据逻辑层(被观察者), 并且建立了一套触发机制,使得数据的变化可以响应到多个表示层上。 (3)实现了一对多的通信机制,支持事件注册机制,支持兴趣分发机制, 当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。 观察者模式的缺点 (1)如果观察者数量过多,则事件通知会耗时较长。 (2)事件通知呈线性关系,如果其中一个观察者处理事件卡壳,则会影响后续的观察者接收该事件。 (3)如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。 (摘自 谭勇德 <>)
(end)
有疑问加站长微信联系(非本文作者)