1.事件總線
事件總線是發佈/訂閱模式的實現,其中發佈者發佈數據,並且訂閱者可以監聽這些數據並基於這些數據作出處理。這使發佈者與訂閱者鬆耦合。發佈者將數據事件發佈到事件總線,總線負責將它們發送給訂閱者。
傳統的實現事件總線的方法會涉及到使用回調。訂閱者通常實現接口,然後事件總線通過接口傳播數據。
使用 Go 的併發模型,大多數地方可以使用 channel
來替代回調。
2.事件總線實例
EvevtBus.go
package EventBus
import "sync"
// 定義數據結構
type DataEvent struct {
Data interface{}
Topic string
}
// DataChannel是一個能接收 DataEvent 的 channel
type DataChannel chan DataEvent
// DataChannelSlice 是一個包含 DataChannels 數據的切片
type DataChannelSlice []DataChannel
// 定義事件總線 EventBus 存儲有關訂閱者感興趣的特定主題的信息
type EventBus struct {
Subscribers map[string]DataChannelSlice
rm sync.RWMutex
}
// 發佈主題 發佈者需要提供廣播給訂閱者所需要的主題和數據
func (eb *EventBus) Publish(topic string, data interface{}) {
eb.rm.RLock()
if chans, found := eb.Subscribers[topic]; found {
// 這樣做是因爲切片引用相同的數組,即使它們是按值傳遞的
// 因此我們正在使用我們的元素創建一個新切片,從而正確地保持鎖定
channels := append(DataChannelSlice{}, chans...) //切片賦值
//使用Goroutine 來避免阻塞發佈者
go func(data DataEvent, dataChannelSlices DataChannelSlice) {
for _, ch := range dataChannelSlices {
ch <- data
}
}(DataEvent{Data: data, Topic: topic}, channels)
}
eb.rm.RUnlock()
}
// 訂閱主題 如傳統方法回調一樣。當發佈者向主題發佈數據時,channel將接收數據。
func (eb *EventBus) Subscribe(topic string, ch DataChannel) {
eb.rm.Lock()
if prev, found := eb.Subscribers[topic]; found {
eb.Subscribers[topic] = append(prev, ch)
} else {
eb.Subscribers[topic] = append([]DataChannel{}, ch)
}
eb.rm.Unlock()
}
main.go調用
package main
import (
"Alang/EventBus/EventBus"
"fmt"
"math/rand"
"time"
)
// 聲明事件總線對象
var eb = &EventBus.EventBus{
Subscribers: map[string]EventBus.DataChannelSlice{},
}
//打印訂閱消息
func printDataEvent(ch string, data EventBus.DataEvent) {
fmt.Printf("Channel: %s; Topic: %s; DataEvent: %v\n", ch, data.Topic, data.Data)
}
//發佈消息
func publishTo(topic string, data string) {
for {
eb.Publish(topic, data)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
}
func main() {
ch1 := make(chan EventBus.DataEvent)
ch2 := make(chan EventBus.DataEvent)
ch3 := make(chan EventBus.DataEvent)
eb.Subscribe("topic1", ch1)
eb.Subscribe("topic2", ch2)
eb.Subscribe("topic3", ch3)
go publishTo("topic1", "Welcome to topic-1")
go publishTo("topic2", "Welcome to topic-2")
for {
select {
case d := <-ch1:
go printDataEvent("ch1", d)
case d := <-ch2:
go printDataEvent("ch2", d)
case d := <-ch3:
go printDataEvent("ch3", d)
}
}
}
運行結果:
侷限性:channel
如果沒有訂閱者消費,會阻塞