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
如果没有订阅者消费,会阻塞