作为学习Go的练习我正在编写一个基本的Queue数据结构。我昨天开始学习接口,我觉得尝试使用它们进行这项练习会很酷。我想要实现的是拥有一个Queue
可以接受任何实现此接口的类型:
type Queuable interface {
Next() *Queuable // This is probably not right
}
基本上我想要的是能够向我的Next()
添加任何具有Queue
方法的类型。所以我尝试的是:
type Node struct {
value interface{}
next *Queuable
}
// Next gets the next object
func (n *Node) Next() *Queuable {
return n.next
}
// Job - A job for the queue
type Job struct {
instruction string
next *Queuable
}
// Next gets the next object
func (j *Job) Next() *Queuable {
return j.next
}
// Queue ...
type Queue struct {
head *Queuable
size int
}
我的方法看起来像:
func (q *Queue) Enqueue(node *Queuable) {
...
}
// Dequeue - Remove a Queueable form the Queue
func (q *Queue) Dequeue() *Queuable {
result := q.head
q.head = q.head.Next()
q.size--
return result
}
我收到了大量的这些错误(基本上是在任何一项有作业的行上):
current.Next undefined (type *Queuable is pointer to interface, not interface)
所以最终我想做的是:
func main() {
queue := NewQueue() // Helper function not pictured
job := &Job{"some instructions", nil}
node := &Node{5, nil}
queue.Enqueue(node) // queue = [node]
queue.Enqueue(job) // queue = [node, job]
queue.Dequeue() // node
queue.Dequeue() // job
}
答案 0 :(得分:1)
不要使用指向接口类型的指针,只使用接口类型。
Queuable
是一种接口类型,因此代码中使用*Queuable
的所有地方都将其更改为Queuable
。例如:
type Queuable interface {
Next() Queuable
}
type Node struct {
value interface{}
next Queuable
}
// Next gets the next object
func (n *Node) Next() Queuable {
return n.next
}
...
在Go中,接口类型的值存储一对:赋给变量的具体值,以及该值的类型描述符。
有关界面内部的更多信息:The Laws of Reflection #The representation of an interface
所以你几乎不需要指向接口的指针。接口包含键值对,其中键可以是指针。指向接口的指针很有意义的情况是,如果要修改传递给另一个函数的接口类型变量的值。
在您的示例中,类型*Job
实现了Queuable
,因为它有一个接收器类型为*Job
的方法,因此在需要Queuable
值的任何地方都有一个值可以使用*Job
(并且将创建并使用类型Queuable
的隐式接口值。)
回到您的示例:
你的Queuable
只定义了一个获取队列中下一个元素的方法,但没有一个方法可以将它排队,这会使这个解决方案失去灵活性。单个Next()
方法仅描述“排队”,但它不是(必然)“queuable”。
要成为 queuable 我还会添加另一种方法:SetNext(Queuable)
type Queuable interface {
Next() Queuable
SetNext(Queuable)
}
它在Node
上的实现可以是:
func (n *Node) SetNext(q Queuable) { n.next = q }
在 Go Playground 上试试。
另请注意,Node
和Job
中存在一些代码重复,即next
字段以及Next()
和SetNext()
方法。我们可以创建一个基本节点实现,例如:
type Base struct {
next Queuable
}
func (b *Base) Next() Queuable { return b.next }
func (b *Base) SetNext(q Queuable) { b.next = q }
现在,您可以在具体的Base
和Node
实施中嵌入此Job
类型,这将实现“继承”next
字段和Next()
和{ {1}}方法,因此您无需在SetNext()
和Node
类型中定义任何这些方法。
这是Job
和Node
的完整实施,不需要其他任何内容:
Job
在 Go Playground 上试试。
答案 1 :(得分:1)
永远不要使用指向接口类型的指针,这已经是指针了!
因此,要使代码生效,请将*Queuable
更改为Queuable
。
type Node struct {
value interface{}
next Queuable
}
// Next gets the next object
func (n *Node) Next() Queuable {
return n.next
}
// Job - A job for the queue
type Job struct {
instruction string
next Queuable
}
但是,您可以使用方法接收器作为指针,具体取决于结构复杂性。虽然如果使用的struct类型很简单,您可以定义使用struct值的方法,这样就可以在内存中分配新地址。如果使用方法接收器作为指针,它将引用内存中结构已占用的地址。
有关接收器的指针与值的规则是可以在指针和值上调用值方法,但只能在指针上调用指针方法。
这个规则的出现是因为指针方法可以修改接收器;在值上调用它们会导致方法接收值的副本,因此将丢弃任何修改。因此,该语言不允许这种错误。
经验法则是,为了保持一致性,最好将方法定义作为指针或方法定义作为整个接口实现的值。