Golang:创建一个接口来抽象一个可能有可变参数的方法



我写了一些代码;人类";。人类每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接口来设置要发布的内容。这里的DescriptionAge方法只是示例,您可以添加所需的任何方法。

优点

  • 避免反射
  • 避免类型参数;适用于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之前的版本
  • 对可以发表的内容种类没有限制
  • 已发布的项目不会隐藏在界面后面
  • 订阅者可以接收多种类型的项目
  • 发布者可以发布多种项目

缺点

  • 需要反射或投射,这很慢,很尴尬,而且不太安全
  • 订阅用户将不得不做额外的工作来弄清楚他们收到了什么样的商品

最新更新