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

huangapple go评论79阅读模式
英文:

Golang : create an interface to abstract a method which may have have variable argument

问题

我写了一些代码来创建“人类”。人类每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  // 通过初始化
HUMAN John is now followed by 0x4901a0   // 通过订阅
There is now 1 observers
HUMAN John is now followed by 0x490300   // 通过订阅
There is now 2 observers

[T+0100ms]

HUMAN John has its birthday      // 100毫秒后:生日到了
Observer1 : HUMAN : John is 1   // 回调
Observer2 : HUMAN : John is 1   // 回调
// ... 继续3秒钟

详细的代码在这里,但问题不在这里
https://goplay.tools/snippet/7qsZ1itcqrS

我的问题是:

我想创建一个与可以订阅事件的事物对应的接口*Producer*。

你可以订阅以下内容:
* 过生日的人类
* 可以检测湿度变化的湿度传感器
* 收到邮件的邮件服务器...

在我的例子中,回调函数的参数是一个*Human*,即年龄发生变化的人...

同样地,对于湿度传感器的给定事件,期望的是传感器结构体。

我的问题是:
- 这样做有意义吗?(这是一个学术问题,事情可以正常工作)
- 如果有意义,应该如何实现?我找不到相关的示例。

可能是这样的:

```go
type Producer interface{ 
     Subscribe(func(<某个可变的东西>))
}

我无法使其正常工作。
而且我很难找到一个好的问题标题。请随意给我一个更好的标题。

英文:

I wrote some code that create "humans". Humans have birthday every 100 ms, and you can subscribe to the event like this:

    pers1 := new(Human)
	pers1.Init(&quot;John&quot;)

	pers1.Subscribe(func(h Human) { fmt.Printf(&quot;Observer1 : %s&quot;, h.String()); return })
	pers1.Subscribe(func(h Human) { fmt.Printf(&quot;Observer2 : %s&quot;, h.String()); return })

	time.Sleep(3 * time.Second)

Output is the following

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

Detailed code is here, but the problem is not there
https://goplay.tools/snippet/7qsZ1itcqrS

My question is the following:

I would like to create an interface Producer corresponding to things producing events I can subscribe on.

You can subscribe to:

  • Human that have birthday
  • Humidity sensors that can detect a change in humidity
  • Mail servers that got a mail...

In my example, the callback function has as argument : a Human. The one whose age changed...

In the same manner, a given event for a humidity sensor would expected the sensor struct.

My question is

  • is there a sense to do such I think? ( This is a scholar question, things work without)
  • if yes, how. I wasn't able to find relevant example

That would be

type Producer interface{ 
     Subscribe( func( &lt; something variable &gt;) )
}

I wasn't able to get something working.
Also I had difficult to find a good title to the question. Feel free to give me a better one.

答案1

得分: 1

根据您的需求,这里有三个可能适合您的选项。

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

// 示例用法
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)
}

// 示例用法
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)
}

// 示例用法
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之前的版本。
  • 没有对可以发布的内容类型的限制。
  • 发布的项目不会被隐藏在接口后面。
  • 订阅者可以接收多种类型的项目。
  • 发布者可以发布多种类型的项目。

缺点

  • 需要使用反射或类型转换,这会导致速度较慢、不太方便且不太安全。
  • 订阅者需要额外的工作来确定接收到的是什么类型的项目。
英文:

Depending on what you need, there are three options that might work for you here.


Option 1: Common Interface for Published Items

Create an interface not only for publishers that can have subscribers, but for the sort of things that those publishers can publish:

type Item interface{
  Description() string
  Age() int
}

type human struct{
  age int
}

func (h *human) Description() string {
  return &quot;human&quot;
}

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 := &amp;humanProducer{}
  addSubscriber(p, func(i Item) {
    fmt.Printf(&quot;Got a %s that is %d years old.\n&quot;, i.Description(), i.Age())
  })
}

You can now set up other types of things to be published by having them implement the Item interface. The Description and Age methods here are just examples - you could add whatever methods there you need.

Pros

  • Avoids reflection.
  • Avoids type parameters; works in versions before Go 1.18.
  • A subscriber can receive multiple kinds of items.
  • A publisher can publish multiple kinds of items.

Cons

  • Published items can't just be anything - you have to define a pre-determined set of functionality that all kinds of published items must have.
  • Published items are hidden behind an interface, so you can only use the functionality exposed in the Item interface unless you start casting or using reflection.

Option 2: Interface using Type Parameters

Add type parameters to the interface itself:

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 := &amp;humanProducer{}
  addSubscriber[*human](p, func(h *human) {
    fmt.Printf(&quot;Got a human that is %d years old.\n&quot;, h.age)
  })
}

Pros

  • Avoids reflection.
  • No restrictions on the sorts of things that can be published.
  • Published items aren't hidden behind an interface.

Cons

  • Publishers can only publish one certain kind of item.
  • Subscribers can only receive one certain kind of item.
  • Any use of the Publisher interface requires use of type parameters. Only works in Go 1.18 or later.

Option 3: Reflection/Casting

Allow publishers to publish anything and use reflection or casting in subscribers to sort out what kind of thing was published:

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 := &amp;humanProducer{}
  addSubscriber(p, func(i any) {
    if h, ok := any.(*human); ok {
      fmt.Printf(&quot;Got a human that is %d years old.\n&quot;, h.age)
    }
  })
}

If using Go pre-1.18, replace any with interface{}. This option is sort of the same thing as option 1, except with an empty Item interface.

Pros

  • Avoids type parameters; works in versions before Go 1.18.
  • No restrictions on the sorts of things that can be published.
  • Published items aren't hidden behind an interface.
  • A subscriber can receive multiple kinds of items.
  • A publisher can publish multiple kinds of items.

Cons

  • Requires reflection or casting, which is slow, awkward, and less safe.
  • Subscribers will have to do extra work to figure out what kind of item they received.

huangapple
  • 本文由 发表于 2022年10月22日 19:41:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/74163222.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定