英文:
map[Task]int64 where Task is an interface
问题
让我们假设我在一个Go库中定义了以下接口:
type Task interface {
Do() error
}
func Register(task Task) { ... }
func GetId(task Task) int64 { ... }
在Register()
函数中,该库将每个任务实例与一个唯一的int64
关联起来。GetId()
函数必须返回给定任务的标识符。
我的初始想法是将这个关联存储为map[Task]int64
。这似乎工作得很好,但有人告诉我,如果实现Task
的对象不可比较相等(例如,包含map
的struct
),这种方法会出错。我仍然需要检查这是否正确。
我打算尝试使用struct { task Task; id int64 }
的切片,然后只需对其进行迭代,但这仍然需要可比较相等的Task
实例。而且据我所知,Go语言中没有身份比较。
我如何实现从Task
实例到其ID的稳健映射?
编辑:到目前为止,提出的两种解决方案都可以工作,但它们的缺点是每个Task实现都必须包含一些重复的代码来处理ID。我可以在一个TaskBase的struct
中提供该代码,然后进行嵌入,但理想情况下,我希望找到一种不需要实现甚至了解ID的解决方案(它们对库来说是内部的,对外部没有意义)。
英文:
Let's say I define the following interface in a Go library:
type Task interface {
Do() error
}
func Register(task Task) { ... }
func GetId(task Task) int64 { ... }
In Register()
, the library associates a unique int64
with each task instance. GetId()
must return the identifier for the given task.
My initial idea was to store the association as a map[Task]int64
. This seems to work fine, but I was told that it would break if an object implementing Task
was not equality-comparable (for example, a struct
containing a map
). I still need to check if this is true.
I was going to try and use a slice of struct { task Task; id int64 }
instead and just iterate over it, but that would still require equality comparable Task
instances. And AFAIU there is no identity comparison in Go.
How can I have a robust mapping from Task
instances to their ID?
EDIT: Both solutions proposed so far work, but they have the disadvantage that every Task implementation has to include some repetitive code to handle the IDs. I could provide that code in a TaskBase struct
that could be embedded, but ideally I would prefer a solution that doesn't require implementations to even know about the IDs (they are internal to the library and have no meaning outside of it).
答案1
得分: 4
package main
import (
"fmt"
"math/rand"
)
type Task interface {
Do() error
ID() int64
}
type XTask struct {
id int64
// other stuff
}
func NewXTask( /task parameters.../) *XTask {
t := &XTask{ /initialize members/}
t.id = Register(t)
// possibly more initialization...
return t
}
func (t *XTask) Do() error { return nil } // stub
func (t *XTask) ID() int64 { return t.id }
var taskRegistry = map[int64]Task{}
func Register(t Task) int64 {
var id int64
for {
id = rand.Int63()
if _, exists := taskRegistry[id]; !exists {
break
}
}
taskRegistry[id] = t
return id
}
func main() {
t1 := NewXTask()
t2 := NewXTask()
fmt.Printf("%x\n", t1.ID())
fmt.Printf("%x\n", t2.ID())
}
英文:
A more complete example: http://play.golang.org/p/1RzDiw7F9t
package main
import (
"fmt"
"math/rand"
)
type Task interface {
Do() error
ID() int64
}
type XTask struct {
id int64
// other stuff
}
func NewXTask( /*task parameters...*/) *XTask {
t := &XTask{ /*initialize members*/}
t.id = Register(t)
// possibly more initialization...
return t
}
func (t *XTask) Do() error { return nil } // stub
func (t *XTask) ID() int64 { return t.id }
var taskRegistry = map[int64]Task{}
func Register(t Task) int64 {
var id int64
for {
id = rand.Int63()
if _, exists := taskRegistry[id]; !exists {
break
}
}
taskRegistry[id] = t
return id
}
func main() {
t1 := NewXTask()
t2 := NewXTask()
fmt.Printf("%x\n", t1.ID())
fmt.Printf("%x\n", t2.ID())
}
I used an ID method as Daniel suggested, and I turned the map backwards from the way you had it. This is because the Task objects know their own ID, so a map from Task to ID is not needed. A map from ID to task however, is useful for guaranteeing uniqueness. It might come in handy some other time if you find yourself with only an ID and you need the corresponding Task object.
Also note this example is not goroutine-safe. If you need that you will have to add synchronization.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论