我写了一些代码;人类";。人类每100毫秒就有一次生日,你可以订阅这样的活动:
pers1 := new(Human)
pers1.Init("John")
pers1.Subscribe(func(h Human) { fmt.Printf("Observer1 : %s", h.String()); return })
pers1.Subscribe(func(h Human) { fmt.Printf("Observer2 : %s", h.String()); return })
time.Sleep(3 * time.Second)
输出为以下
HUMAN John is born // by init
HUMAN John is now followed by 0x4901a0 // by subscribe
There is now 1 observers
HUMAN John is now followed by 0x490300 // by subscribe
There is now 2 observers
[T+0100ms]
HUMAN John has its birthday // after 100ms : birthday happens
Observer1 : HUMAN : John is 1 // callback
Observer2 : HUMAN : John is 1 // callback
// ... continue for 3 seconds
详细的代码在这里,但问题不在那里https://goplay.tools/snippet/7qsZ1itcqrS
我的问题如下:
我想创建一个界面Producer,对应于我可以订阅的事件。
您可以订阅:
- 过生日的人
- 可以检测湿度变化的湿度传感器
- 收到邮件的邮件服务器
在我的例子中,回调函数有一个参数:一个Human。那个年龄变了的。。。
以相同的方式,湿度传感器的给定事件将预期传感器结构。
我的问题是
- 我认为这样做有意义吗?(这是一个学者的问题,没有问题就行(
- 如果是,如何。我找不到相关的例子
那就是
type Producer interface{
Subscribe( func( < something variable >) )
}
我没能做点什么。此外,我很难为这个问题找到一个好的标题。请随意给我一个更好的。
根据您的需要,这里有三个选项可能对您有用。
选项1:已发布项目的通用接口
创建一个接口,不仅用于可以拥有订阅者的发布者,还用于这些发布者可以发布的内容:
type Item interface{
Description() string
Age() int
}
type human struct{
age int
}
func (h *human) Description() string {
return "human"
}
func (h *human) Age() int {
return h.age
}
type Publisher interface{
Subscribe(func(Item))
}
type humanProducer struct{
subscribers []func(Item)
}
func (hp *humanProducer) Subscribe(f func(Item) {
hp.subscribers = append(hp.subscribers, f)
}
// Example use
func addSubscriber(p Publisher, f func(Item)) {
p.Subscribe(f)
}
func main() {
hp := &humanProducer{}
addSubscriber(p, func(i Item) {
fmt.Printf("Got a %s that is %d years old.n", i.Description(), i.Age())
})
}
现在,您可以通过让其他类型的内容实现Item
接口来设置要发布的内容。这里的Description
和Age
方法只是示例,您可以添加所需的任何方法。
优点
- 避免反射
- 避免类型参数;适用于Go 1.18之前的版本
- 订阅者可以接收多种类型的项目
- 发布者可以发布多种项目
缺点
- 发布的项目不能只是任何东西——您必须定义一组预先确定的功能,所有类型的发布项目都必须具有这些功能
- 已发布的项隐藏在接口后面,因此除非开始强制转换或使用反射,否则只能使用
Item
接口中公开的功能
选项2:使用类型参数的接口
将类型参数添加到接口本身:
type human struct{
age int
}
type Publisher[T any] interface{
Subscribe(func(T))
}
type humanProducer struct{
subscribers []func(*human)
}
func (hp *humanProducer) Subscribe(f func(*human) {
hp.subscribers = append(hp.subscribers, f)
}
// Example use
func addSubscriber[T any](p Publisher[T], f func(T)) {
p.Subscribe(f)
}
func main() {
hp := &humanProducer{}
addSubscriber[*human](p, func(h *human) {
fmt.Printf("Got a human that is %d years old.n", h.age)
})
}
优点
- 避免反射
- 对可以发表的内容种类没有限制
- 已发布的项目不会隐藏在界面后面
缺点
- 发布者只能发布一种特定的项目
- 订阅服务器只能接收一种特定类型的项目
- 任何对
Publisher
接口的使用都需要使用类型参数。仅适用于Go 1.18或更高版本
选项3:反射/投射
允许出版商发布任何内容,并使用反射或选角订阅者来整理发布的内容:
type human struct{
age int
}
type Publisher interface{
Subscribe(func(any))
}
type humanProducer struct{
subscribers []func(any)
}
func (hp *humanProducer) Subscribe(f func(any) {
hp.subscribers = append(hp.subscribers, f)
}
// Example use
func addSubscriber(p Publisher, f func(any)) {
p.Subscribe(f)
}
func main() {
hp := &humanProducer{}
addSubscriber(p, func(i any) {
if h, ok := any.(*human); ok {
fmt.Printf("Got a human that is %d years old.n", h.age)
}
})
}
如果使用Go 1.18之前的版本,请将any
替换为interface{}
。这个选项与选项1有点相同,只是有一个空的Item
接口。
优点
- 避免类型参数;适用于Go 1.18之前的版本
- 对可以发表的内容种类没有限制
- 已发布的项目不会隐藏在界面后面
- 订阅者可以接收多种类型的项目
- 发布者可以发布多种项目
缺点
- 需要反射或投射,这很慢,很尴尬,而且不太安全
- 订阅用户将不得不做额外的工作来弄清楚他们收到了什么样的商品