为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
慕丝7291255 2023-03-07 11:36:36
我写了一些创造“人类”的代码。人类每 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 initHUMAN John is now followed by 0x4901a0   // by subscribeThere is now 1 observersHUMAN John is now followed by 0x490300   // by subscribeThere is now 2 observers[T+0100ms]HUMAN John has its birthday      // after 100ms : birthday happensObserver1 : HUMAN : John is 1   // callbackObserver2 : HUMAN : John is 1   // callback// ... continue for 3 seconds详细代码在这里,但问题不在 https://goplay.tools/snippet/7qsZ1itcqrS我的问题如下:我想创建一个接口Producer对应于产生我可以订阅的事件的事物。您可以订阅:过生日的人可检测湿度变化的湿度传感器收到邮件的邮件服务器...在我的例子中,回调函数有一个参数:一个Human。那个年龄变了...以相同的方式,湿度传感器的给定事件将期望传感器结构。我的问题是我认为这样做有意义吗?(这是一个学者问题,没有工作)如果是,如何。我找不到相关的例子那将是type Producer interface{      Subscribe( func( < something variable >) )}我无法得到一些工作。我也很难为这个问题找到一个好的标题。随意给我一个更好的。
查看完整描述

1 回答

?
波斯汪

TA贡献1811条经验 获得超4个赞

根据您的需要,此处提供三个可能适合您的选项。


选项 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)

    }

  })

}

如果使用 1.18 之前的 Go,请替换anyinterface{}. 此选项与选项 1 有点相同,只是Item接口为空。

优点

  • 避免类型参数;适用于 Go 1.18 之前的版本。

  • 对可以发布的内容没有限制。

  • 已发布的项目不会隐藏在界面后面。

  • 订阅者可以接收多种项目。

  • 发布者可以发布多种项目。

缺点

  • 需要反射或投射,这是缓慢、笨拙且不太安全的。

  • 订户将不得不做额外的工作来弄清楚他们收到了什么样的项目。


查看完整回答
反对 回复 2023-03-07
  • 1 回答
  • 0 关注
  • 124 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信