简介
观察者是一种行为设计模式, 允许一个对象将其状态的改变通知其他对象。
观察者模式提供了一种用于任何实现了订阅者接口的对象的机制,可对其事件进行订阅和取消订阅。
概念示例
在电商网站中, 商品时不时地会出现缺货情况。 可能会有客户对于缺货的特定商品表现出兴趣。 这一问题有三种解决方案:
- 客户定时去电商网站上查看商品是否上新了
- 客户关注电商网站,电商网站向用户推送所有上新的商品信息
- 客户只订阅自己感兴趣的特定商品,只有该商品上新时才会收到通知(一件商品支持任意数量的客户订阅)
通过比较选项 3 最具有可行性,实际上我们现在的电商网站使用的就是这种模式,比如你想购买的某个商品卖断货了,下方有个到货通知按钮,你点击之后就可以在这个商品补货的第一时间收到通知了,这其实就是观察者模式的思想。
观察者模式的主要组成部分有:
- 任何事件发生时负责发布事件的主体(发布者 publisher)
- 订阅了主体事件并在事件发生时收到通知的观察者(watcher、subscriber)
subject.go 主体
package main
type Subject interface {
register(observer Observer)
deregister(observcer Observer)
notifyAll()
}
item.go 具体主体
package main
import "fmt"
type Item struct {
observerList []Observer
name string
inStock bool
}
func newItem(name string) *Item {
return &Item {
name: name,
}
}
func (i *Item) updateAvaiability() {
fmt.Printf("Item %s is now in stock\n", i.name)
i.inStock = true
i.notifyAll()
}
func (i *Item) register(o Observer) {
i.observerList = append(i.observerList, o)
}
func (i *Item) deregister(o Observer) {
i.observerList = removeFromSlice(i.observerList, o)
}
func (i *Item) notifyAll() {
for _, observer := range i.observerList {
observer.update(i.name)
}
}
func removeFromSlice(observerList []Observer, observerToRemove Observer) []Observer {
// 删除逻辑是:找到待删除的 observer 后交换到 observerList 末尾方便处理
observerListLength := len(observerList)
for i, observer := range observerList {
if observerToRemove.getID() == observer.getID() {
observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
return observerList[:observerListLength-1]
}
}
return observerList
}
observer.go 观察者
package main
type Observer interface {
update(string)
getID() string
}
customer.go 具体观察者
package main
import "fmt"
type Customer struct {
id string // 指定用户的标识符
}
func (c *Customer) update(itemName string) {
// item 到货了,给它的观察者发送通知
fmt.Printf("sending email to customer %s for item %s\n", c.id, itemName)
}
func (c *Customer) getID() string {
return c.id
}
main.go 客户端代码
package main
func main() {
// nike shirt 缺货
shirtItem := newItem("Nike Shirt")
observerFirst := &Customer{id: "abc@gmail.com"}
observerSecond := &Customer{id: "xyz@gmial.com"}
// 两个用户点击到货通知
shirtItem.register(observerFirst)
shirtItem.register(observerSecond)
// 到货通知
shirtItem.updateAvailability()
}
output.txt: 执行结果
Item Nike Shirt is now in stock
Sending email to customer abc@gmail.com for item Nike Shirt
Sending email to customer xyz@gmail.com for item Nike Shirt
Full Code In One File
package main
import "fmt"
type Pulisher interface {
register(watcher Watcher)
deregister(watcher Watcher)
notifyAll()
}
type Watcher interface {
update(item string)
getID() string
}
type Item struct {
wacherset []Watcher
name string
inStock bool
}
func newItem(name string) *Item {
return &Item{name: name}
}
func (i *Item) updateAvailability() {
fmt.Printf("Item %s is now in stock\n", i.name)
i.inStock = true
i.notifyAll() // 通知所有观察者
}
func (i *Item) register(w Watcher) {
i.wacherset = append(i.wacherset, w)
}
func (i *Item) deregister(w Watcher) {
remove(i.wacherset, w)
}
func (i *Item) notifyAll() {
for _, watcher := range i.wacherset {
watcher.update(i.name)
}
}
func remove(watcherSet []Watcher, target Watcher) {
watcherLen := len(watcherSet)
for i, watcher := range watcherSet {
if watcher.getID() == target.getID() {
watcherSet[watcherLen-1], watcherSet[i] = watcherSet[i], watcherSet[watcherLen-1]
watcherSet = watcherSet[:watcherLen-1]
return
}
}
}
type customer struct {
email string
}
func (c *customer) update(item string) {
fmt.Printf("Sending email to customer %s for item %s\n", c.email, item)
}
func (c *customer) getID() string {
return c.email
}
func main() {
item := newItem("Samsung Galaxy S20")
customer1 := &customer{email: "john.doe@example.com"}
customer2 := &customer{email: "jane.doe@example.com"}
item.register(customer1)
item.register(customer2)
item.updateAvailability()
}
项目中的观察者模式使用
package main
import (
"context"
"crypto/sha256"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"sync/atomic"
)
const (
defaultResultChanSize = 8
Modified EventType = "modified"
Added EventType = "added"
baseDir = "/Users/apple/Study/Go_Study/test_code/data/"
)
// 为可扩展性考虑
type BootStrapService interface {
GetStorageService() *Storage
}
type bootStrapService struct {
ctx context.Context
lock sync.RWMutex
cancel context.CancelFunc
configs *Storage
dc *diskCache
}
func getStorageService(ctx context.Context) *Storage {
return &Storage{
ctx: ctx,
objects: make(map[string]string),
watchers: watcherSet{},
keyWatchers: map[string]watcherSet{},
wlock: sync.RWMutex{},
}
}
type EventType string
type ConfigEvent struct {
KeyLink string
Type EventType
Config string
}
func newEvent(key string, old, new string) *ConfigEvent {
if old == "" {
return &ConfigEvent{
KeyLink: key,
Type: Added,
Config: new,
}
}
if checksum(old) != checksum(new) {
return &ConfigEvent{
KeyLink: key,
Type: Modified,
Config: new,
}
}
// 未发生更改
return nil
}
type watcher struct {
result chan ConfigEvent
stopped *int32
onRemove func(*watcher)
}
func (w *watcher) ResultChan() <-chan ConfigEvent {
return w.result
}
func (w *watcher) Stop() {
if atomic.CompareAndSwapInt32(w.stopped, 0, 1) {
if w.onRemove != nil {
w.onRemove(w)
}
}
}
// 内存型存储服务,它可以作为发布者
// 用户可以 watch 指定 key 对应配置的变更
type watcherSet map[*watcher]struct{}
func (ws watcherSet) Add(w *watcher) {
ws[w] = struct{}{}
}
func (ws watcherSet) Delete(w *watcher) {
delete(ws, w)
}
type Storage struct {
ctx context.Context
wlock sync.RWMutex
objects map[string]string
keyWatchers map[string]watcherSet
watchers watcherSet // 全局
}
func New(ctx context.Context) *Storage {
return &Storage{
ctx: ctx,
objects: make(map[string]string),
watchers: watcherSet{},
keyWatchers: map[string]watcherSet{},
}
}
func (s *Storage) Get(key string) (string, error) {
s.wlock.RLock()
defer s.wlock.RUnlock()
return s.objects[key], nil
}
func (s *Storage) Put(key, value string) {
event := newEvent(key, s.objects[key], value)
s.notifyAll(event)
s.objects[key] = value
}
func (s *Storage) Watch(key string) *watcher {
s.wlock.Lock()
defer s.wlock.Unlock()
w := &watcher{
result: make(chan ConfigEvent, defaultResultChanSize),
stopped: new(int32),
onRemove: func(w *watcher) {
s.wlock.Lock()
defer s.wlock.Unlock()
ws, ok := s.keyWatchers[key]
if ok {
delete(ws, w)
}
close(w.result)
},
}
ws, ok := s.keyWatchers[key]
if !ok {
ws = watcherSet{}
s.keyWatchers[key] = ws
}
ws.Add(w)
return w
}
func (s *Storage) WatchAll() *watcher {
s.wlock.RLock()
defer s.wlock.RUnlock()
wa := &watcher{
result: make(chan ConfigEvent, defaultResultChanSize),
stopped: new(int32),
onRemove: func(w *watcher) {
s.wlock.Lock()
defer s.wlock.Unlock()
delete(s.watchers, w)
close(w.result)
},
}
s.watchers.Add(wa)
return wa
}
func (s *Storage) notifyAll(event *ConfigEvent) {
// 全局的
for watcher := range s.watchers {
watcher.result <- *event
}
// 针对特定key的
watcher := s.keyWatchers[event.KeyLink]
for w := range watcher {
w.result <- *event
}
}
func checksum(config string) string {
h := sha256.New()
h.Write([]byte(config))
return string(h.Sum(nil))
}
type diskCache struct {
ctx context.Context
configs *Storage
baseDir string
}
func newDiskCache(ctx context.Context, configs *Storage) *diskCache {
dc := &diskCache{
ctx: ctx,
configs: configs,
baseDir: baseDir,
}
w := configs.WatchAll()
go dc.sync(w)
return dc
}
func (d *diskCache) sync(w *watcher) {
for {
select {
case event, ok := <-w.result:
fmt.Printf("persistent store watcher receive update event, new config 'key:value' is '%v:%v'\n\n", event.KeyLink, event.Config)
if !ok {
return
}
d.writeToDisk(event.Config)
case <-d.ctx.Done():
return
}
}
}
func (d *diskCache) writeToDisk(config string) {
file := filepath.Join(d.baseDir, config)
fmt.Printf("new config already write to file %v\n", file)
err := writeFile(file, []byte(config), 0644)
if err != nil {
log.Fatalf("write data to file %s failed, err: %v", file, err)
}
}
func writeFile(path string, data []byte, perm os.FileMode) error {
file, err := OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(data)
return err
}
func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
if PathExist(path) {
return os.OpenFile(path, flag, perm)
}
if err := CreateDirectory(filepath.Dir(path)); err != nil {
return nil, err
}
return os.OpenFile(path, flag, perm)
}
func PathExist(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func CreateDirectory(dirPath string) error {
f, e := os.Stat(dirPath)
if e != nil {
if os.IsNotExist(e) {
return os.MkdirAll(dirPath, 0755)
}
return fmt.Errorf("failed to create dir %s: %v", dirPath, e)
}
if !f.IsDir() {
return fmt.Errorf("failed to create dir %s: file already exist", dirPath)
}
return e
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s := getStorageService(ctx)
dc := newDiskCache(ctx, s)
bs := bootStrapService{
ctx: ctx,
lock: sync.RWMutex{},
cancel: cancel,
configs: s,
dc: dc,
}
w1 := bs.configs.Watch("smy")
w2 := bs.configs.Watch("hello")
w3 := bs.configs.Watch("world")
handlerPut := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key, value := r.PostFormValue("key"), r.PostFormValue("value")
fmt.Println("receive POST Request For", key, value)
s.Put(key, value)
w.WriteHeader(http.StatusOK)
})
handlerGet := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.URL.Query().Get("key")
fmt.Println("receive GET Request For Key", key)
value, err := s.Get(key)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
}
if value == "" {
w.Write([]byte("key not found"))
} else {
w.Write([]byte(value))
}
})
http.Handle("/put", handlerPut)
http.Handle("/get", handlerGet)
fmt.Println("starting server in goroutine, listening :8080")
go http.ListenAndServe(":8080", nil)
fmt.Printf("\nstart watch key changing in main groutine...\n\n")
for {
select {
case e := <-w1.result:
fmt.Printf("smy config changed, its event type is %s and its new value is %s\n\n", e.Type, e.Config)
case e := <-w2.result:
fmt.Printf("hello config changed, its event type is %s and its new value is %s\n\n", e.Type, e.Config)
case e := <-w3.result:
fmt.Printf("world config changed, its event type is %s and its new value is %s\n\n", e.Type, e.Config)
case <-ctx.Done():
return
}
}
}
未存入时的 Get 测试
curl "localhost:8080/get?key=smy"
curl "localhost:8080/get?key=hello"
curl "localhost:8080/get?key=world"
压入新 config
- put 操作
curl -X PUT localhost:8080/put -d "key=hello&value=hellohello"
- 持久化
- 更新操作
curl -X PUT localhost:8080/put -d "key=smy&value=smysmy"
curl -X PUT localhost:8080/put -d "key=smy&value=smyupdate"
再次测试 Get
curl "localhost:8080/get?key=smy"