手撸golang 行为型设计模式 观察者模式

缘起

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

观察者模式

观察者模式(Observer Pattern)又叫作公布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式, 或隶属者(Dependent)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变动时,所有依赖它的对象都会失去告诉并被自动更新,属于行为型设计模式。(摘自 谭勇德 <<设计模式就该这样学>>)

场景

  • 某智能app, 需增加自定义闹铃的性能
  • 闹铃可设定工夫, 以及是否每日反复
  • 可设定多个闹铃
  • 依据观察者模式, 每个闹铃对象, 都是工夫服务的观察者, 监听工夫变动的事件.

设计

  • ITimeService: 定义工夫服务的接口, 承受观察者的注册和登记
  • ITimeObserver: 定义工夫观察者接口, 接管工夫变动事件的告诉
  • tMockTimeService: 虚构的工夫服务, 自定义工夫倍率以不便时钟相干的测试
  • AlarmClock: 闹铃的实现类, 实现ITimeObserver接口以订阅工夫变动告诉

单元测试

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

package behavioral_patternsimport (    "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:002021-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)PASSok      command-line-arguments  69.012s

ITimeService.go

定义工夫服务的接口, 承受观察者的注册和登记

package observertype ITimeService interface {    Attach(observer ITimeObserver)    Detach(id string)}

ITimeObserver.go

定义工夫观察者接口, 接管工夫变动事件的告诉

package observerimport "time"type ITimeObserver interface {    ID() string    TimeElapsed(now *time.Time)}

tMockTimeService.go

虚构的工夫服务, 自定义工夫倍率以不便时钟相干的测试

package observerimport (    "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 observerimport (    "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 = 0func 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() < now.Unix() {        t = t.Add(24*time.Hour)    }    fmt.Printf("%s.next = %s\n", me.name, t.Format("2006-01-02 15:04:05"))    return &t}func (me *AlarmClock) ID() string {    return me.name}func (me *AlarmClock) TimeElapsed(now *time.Time) {    it := me.next    if it == nil {        return    }    if now.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)