溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言設計模式之如何實現觀察者模式

發布時間:2022-08-12 10:11:00 來源:億速云 閱讀:154 作者:iii 欄目:開發技術

這篇文章主要講解了“Go語言設計模式之如何實現觀察者模式”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Go語言設計模式之如何實現觀察者模式”吧!

    觀察者模式

    咱們先來看一下觀察者模式的概念,我盡量加一些自己的理解,讓它變成咱們都能理解的大俗話:

    概念

    觀察者模式 (Observer Pattern),定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知,依賴對象在收到通知后,可自行調用自身的處理程序,實現想要干的事情,比如更新自己的狀態。

    發布者對觀察者唯一了解的是它實現了某個接口(觀察者接口)。這種松散耦合的設計最大限度地減少了對象之間的相互依賴,因此使我們能夠構建靈活的系統來處理主體的變化。

    我的理解

    上面這段話看完,相信幾乎對于理解觀察者模式能起到的作用微乎其微,類似于現實職場里加班對項目進度起到的作用一樣,加班的時候誰還沒打過幾把王者榮耀,嘿。下面我用自己的理解再給你們嘮一下。

    觀察者模式也經常被叫做發布 - 訂閱(Publish/Subscribe)模式、上面說的定義對象間的一種一對多依賴關系,一 - 指的是發布變更的主體對象,多 - 指的是訂閱變更通知的訂閱者對象。

    發布的狀態變更信息會被包裝到一個對象里,這個對象被稱為事件,事件一般用英語過去式的語態來命名,比如用戶注冊時,用戶模塊在用戶創建好后發布一個事件 UserCreated 或者 UserWasCreated 都行,這樣從名字上就能看出,這是一個已經發生過的事件。

    事件發布給訂閱者的過程,其實就是遍歷一下已經注冊的事件訂閱者,逐個去調用訂閱者實現的觀察者接口方法,比如叫 handleEvent 之類的方法,這個方法的參數一般就是當前的事件對象。

    至于很多人會好奇的,事件的處理是不是異步的?主要看我們的需求是什么,一般情況下是同步的,即發布事件后,觸發事件的方法會阻塞等到全部訂閱者返回后再繼續,當然也可以讓訂閱者的處理異步執行,完全看我們的需求。

    大部分場景下其實是同步執行的,單體架構會在一個數據庫事務里持久化因為主體狀態變更,而需要更改的所有實體類。

    微服務架構下常見的做法是有一個事件存儲,訂閱者接到事件通知后,會把事件先存到事件存儲里,這兩步也需要在一個事務里完成才能保證最終一致性,后面會再有其他線程把事件從事件存儲里搞到消息設施里,發給其他服務,從而在微服務架構下實現各個位于不同服務的實體間的最終一致性。

    所以觀察者模式,從程序效率上看,大多數情況下沒啥提升,更多的是達到一種程序結構上的解耦,讓代碼不至于那么難維護。

    Go 實現觀察者模式

    說了這么多,我們再看下用 Go 怎么實現最簡單的觀察者模式:

    package main
    import "fmt"
    // Subject 接口,它相當于是發布者的定義
    type Subject interface {
    	Subscribe(observer Observer)
    	Notify(msg string)
    }
    // Observer 觀察者接口
    type Observer interface {
    	Update(msg string)
    }
    // Subject 實現
    type SubjectImpl struct {
    	observers []Observer
    }
    // Subscribe 添加觀察者(訂閱者)
    func (sub *SubjectImpl) Subscribe(observer Observer) {
    	sub.observers = append(sub.observers, observer)
    }
    // Notify 發布通知
    func (sub *SubjectImpl) Notify(msg string) {
    	for _, o := range sub.observers {
    		o.Update(msg)
    	}
    }
    // Observer1 Observer1
    type Observer1 struct{}
    // Update 實現觀察者接口
    func (Observer1) Update(msg string) {
    	fmt.Printf("Observer1: %s\n", msg)
    }
    // Observer2 Observer2
    type Observer2 struct{}
    // Update 實現觀察者接口
    func (Observer2) Update(msg string) {
    	fmt.Printf("Observer2: %s\n", msg)
    }
    func main(){
    	sub := &SubjectImpl{}
    	sub.Subscribe(&Observer1{})
    	sub.Subscribe(&Observer2{})
    	sub.Notify("Hello")
    }

    這就是 Go 實現觀察者模式的代碼,實際應用的時候,一般會定義個事件總線 EventBus 或者事件分發器 Event Dispatcher,來管理事件和訂閱者間的關系和分發事件,它們兩個就是名不一樣,角色定位一樣。

    Go 實現事件總線

    下面我們實現一個支持以下功能的事件總線

    • 異步不阻塞

    • 支持任意參數值

    這個代碼不是我自己寫的,出處見代碼注釋首行。

    代碼

    // 代碼來自https://lailin.xyz/post/observer.html
    package eventbus
    import (
    	"fmt"
    	"reflect"
    	"sync"
    )
    // Bus Bus
    type Bus interface {
    	Subscribe(topic string, handler interface{}) error
    	Publish(topic string, args ...interface{})
    }
    // AsyncEventBus 異步事件總線
    type AsyncEventBus struct {
    	handlers map[string][]reflect.Value
    	lock     sync.Mutex
    }
    // NewAsyncEventBus new
    func NewAsyncEventBus() *AsyncEventBus {
    	return &AsyncEventBus{
    		handlers: map[string][]reflect.Value{},
    		lock:     sync.Mutex{},
    	}
    }
    // Subscribe 訂閱
    func (bus *AsyncEventBus) Subscribe(topic string, f interface{}) error {
    	bus.lock.Lock()
    	defer bus.lock.Unlock()
    	v := reflect.ValueOf(f)
    	if v.Type().Kind() != reflect.Func {
    		return fmt.Errorf("handler is not a function")
    	}
    	handler, ok := bus.handlers[topic]
    	if !ok {
    		handler = []reflect.Value{}
    	}
    	handler = append(handler, v)
    	bus.handlers[topic] = handler
    	return nil
    }
    // Publish 發布
    // 這里異步執行,并且不會等待返回結果
    func (bus *AsyncEventBus) Publish(topic string, args ...interface{}) {
    	handlers, ok := bus.handlers[topic]
    	if !ok {
    		fmt.Println("not found handlers in topic:", topic)
    		return
    	}
    	params := make([]reflect.Value, len(args))
    	for i, arg := range args {
    		params[i] = reflect.ValueOf(arg)
    	}
    	for i := range handlers {
    		go handlers[i].Call(params)
    	}
    }

    單測

    package eventbus
    import (
    	"fmt"
    	"testing"
    	"time"
    )
    func sub1(msg1, msg2 string) {
    	time.Sleep(1 * time.Microsecond)
    	fmt.Printf("sub1, %s %s\n", msg1, msg2)
    }
    func sub2(msg1, msg2 string) {
    	fmt.Printf("sub2, %s %s\n", msg1, msg2)
    }
    func TestAsyncEventBus_Publish(t *testing.T) {
    	bus := NewAsyncEventBus()
    	bus.Subscribe("topic:1", sub1)
    	bus.Subscribe("topic:1", sub2)
    	bus.Publish("topic:1", "test1", "test2")
    	bus.Publish("topic:1", "testA", "testB")
    	time.Sleep(1 * time.Second)
    }

    感謝各位的閱讀,以上就是“Go語言設計模式之如何實現觀察者模式”的內容了,經過本文的學習后,相信大家對Go語言設計模式之如何實現觀察者模式這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女