【问题标题】:Using Interfaces to Create a Queue for Arbitrary Types使用接口为任意类型创建队列
【发布时间】:2016-02-24 07:27:07
【问题描述】:

作为学习围棋的练习,我正在编写一个基本的队列数据结构。我昨天开始学习接口,我认为尝试在这个练习中使用它们会很酷。我想要完成的是有一个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
}

【问题讨论】:

    标签: go interface queue


    【解决方案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() 方法仅描述它是“queued”,但它不是(必然)“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 }
    

    现在你可以在具体的Node 和Job 实现中嵌入这个Base 类型,这将“继承”next 字段和Next() 和SetNext() 方法,所以你没有在 Node 和 Job 类型上定义任何这些。

    这是Node和Job的完整实现,不需要其他任何东西:

    type Node struct {
        *Base
        value interface{}
    }
    
    type Job struct {
        *Base
        instruction string
    }
    

    在Go Playground 上试试这个。

    【讨论】:

      【解决方案2】:

      永远不要使用指向接口类型的指针,这已经是一个指针了!

      因此,要使代码正常工作,请将 *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
      }
      

      但是,您可以使用方法接收器作为指针,具体取决于结构的复杂性。虽然如果使用的结构类型很简单,您可以定义使用结构值的方法,这样会在内存中分配一个新地址。如果将方法接收器用作指针,它将引用内存中结构已占用的地址。

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

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

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

      【讨论】:

      • 答案也很好。这为我阐明了指针和接口以及它们如何交互。
      • 没问题,很高兴我的回答对你有帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-07
      • 2015-01-26
      • 1970-01-01
      • 2018-12-04
      • 1970-01-01
      • 2020-09-22
      相关资源
      最近更新 更多