使用接口为任意类型创建队列

时间:2016-02-24 07:27:08

标签: go interface queue

作为学习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
}

2 个答案:

答案 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 上试试。

另请注意,NodeJob中存在一些代码重复,即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 }

现在,您可以在具体的BaseNode实施中嵌入此Job类型,这将实现“继承”next字段和Next()和{ {1}}方法,因此您无需在SetNext()Node类型中定义任何这些方法。

这是JobNode的完整实施,不需要其他任何内容:

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值的方法,这样就可以在内存中分配新地址。如果使用方法接收器作为指针,它将引用内存中结构已占用的地址。

有关接收器的指针与值的规则是可以在指针和值上调用值方法,但只能在指针上调用指针方法。

这个规则的出现是因为指针方法可以修改接收器;在值上调用它们会导致方法接收值的副本,因此将丢弃任何修改。因此,该语言不允许这种错误。

经验法则是,为了保持一致性,最好将方法定义作为指针或方法定义作为整个接口实现的值。