英文:
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("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)
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( < something variable >) )
}
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
接口来设置其他类型的发布内容。这里的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)
}
// 示例用法
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 "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())
})
}
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 := &humanProducer{}
addSubscriber[*human](p, func(h *human) {
fmt.Printf("Got a human that is %d years old.\n", 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 := &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)
}
})
}
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论