11

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

 3 years ago
source link: https://studygolang.com/articles/33308
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

手撸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() < 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)

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK