使用接口创建适用于任意类型的队列

huangapple go评论72阅读模式
英文:

Using Interfaces to Create a Queue for Arbitrary Types

问题

作为学习Go语言的练习,我正在编写一个基本的队列数据结构。昨天我开始学习接口,我觉得尝试在这个练习中使用它们会很酷。我想要实现的目标是创建一个Queue,它可以接受任何实现以下接口的类型:

type Queuable interface {
  Next() *Queuable  // 这可能不正确
}

基本上,我想要能够将任何具有Next()方法的类型添加到我的队列中。所以我尝试了以下代码:

type Node struct {
	value interface{}
	next  *Queuable
}

// Next 获取下一个对象
func (n *Node) Next() *Queuable {
	return n.next
}

// Job - 队列中的任务
type Job struct {
	instruction string
	next        *Queuable
}

// Next 获取下一个对象
func (j *Job) Next() *Queuable {
	return j.next
}

// Queue ...
type Queue struct {
	head *Queuable
	size int
}

我的方法看起来像这样:

func (q *Queue) Enqueue(node *Queuable) {
	...
}

// Dequeue - 从队列中移除一个Queueable
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()  // 辅助函数未显示
  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
}
英文:

As an exercise for learning Go I am writing a basic Queue data structure. I started learning about interfaces yesterday I thought it would be cool to try and use them for this exercise. What I am trying to accomplish is to have a Queue that can accept any type that implements this interface:

type Queuable interface {
  Next() *Queuable  // This is probably not right
}

Basically what I want is to be able to add any type that has a Next() method to my Queue. So what I tried was:

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
}

And my methods looking like:

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
}

I'm getting a ton of these errors (basically on any line with an assignment):

current.Next undefined (type *Queuable is pointer to interface, not interface)

So ultimately what I would like to do would be:

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
}

答案1

得分: 1

不要使用指向接口类型的指针,而是直接使用接口类型。

Queuable 是一个接口类型,在你的代码中,无论何处使用了 *Queuable,都将其改为 Queuable。例如:

type Queuable interface {
    Next() Queuable
}

type Node struct {
    value interface{}
    next  Queuable
}

// Next 获取下一个对象
func (n *Node) Next() Queuable {
    return n.next
}

...

在 Go 中,接口类型的值存储了一对信息:变量分配的具体值和该值的类型描述符。

关于接口的内部机制,可以参考:反射定律 #接口的表示

因此,你几乎不需要指向接口的指针。接口包含一个键值对,其中键可以是指针。指向接口的指针只在以下罕见情况下有意义:如果你想修改传递给另一个函数的接口类型变量的值。

在你的示例中,类型 *Job 实现了 Queuable,因为它具有接收者类型为 *Job 的方法,所以在需要 Queuable 值的任何地方,都可以使用 *Job 值(并且将创建并使用一个隐式接口值,类型为 Queuable)。

回到你的示例:

你的 Queuable 只定义了一个方法来获取队列中的下一个元素,但没有定义一个方法来将其入队,这将使这个解决方案失去灵活性。一个单独的 Next() 方法只描述了它是“排队”的,但不一定是“可排队”的。

为了使其“可排队”,我还会添加另一个方法: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 }

现在,你可以在具体的 NodeJob 实现中嵌入这个 Base 类型,它将“继承”next 字段和 Next()SetNext() 方法,因此你不需要在 NodeJob 类型上定义这些方法。

这是 NodeJob 的完整实现,不需要其他内容:

type Node struct {
    *Base
    value interface{}
}

type Job struct {
    *Base
    instruction string
}

Go Playground 上试一试。

英文:

Don't use pointer to an interface type, just the interface type.

Queuable is an interface type, so everywhere in your code where you used *Queuable, change it to Queuable. For example:

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
}

...

In Go a value of interface type stores a pair: the concrete value assigned to the variable, and that value's type descriptor.

More about interface's internals: The Laws of Reflection #The representation of an interface

So you almost never need a pointer to interface. An interface contains a key-value pair where the key may be a pointer. The rare case when a pointer to interface makes sense is if you want to modify the value of a variable of interface type passed to another function.

In your example the type *Job implements Queuable because it has a method with receiver type *Job, and so everywhere where a value of Queuable is required, a value of *Job can be used (and an implicit interface value of type Queuable will be created and used).

Getting back to your example:

Your Queuable only defines a method to get the next element in the queue, but not one to enqueue it which will make this solution lose flexibility. A single Next() method only describes that it is "queued" but it is not (necessarily) "queuable".

To be queuable I would also add another method: SetNext(Queuable)

type Queuable interface {
    Next() Queuable
    SetNext(Queuable)
}

Its implementation on Node can be for example:

func (n *Node) SetNext(q Queuable) { n.next = q }

Try it on the Go Playground.

Also note that there is some code duplication in Node and Job, being the next field and Next() and SetNext() methods. We could create a base node implementation, e.g.:

type Base struct {
    next Queuable
}

func (b *Base) Next() Queuable     { return b.next }
func (b *Base) SetNext(q Queuable) { b.next = q }

And now you can embed this Base type in your concrete Node and Job implementations which will "inherit" the next field and Next() and SetNext() methods, so you don't have to define any of these on the Node and Job types.

This is the full implementation of Node and Job, nothing else is required:

type Node struct {
    *Base
    value interface{}
}

type Job struct {
    *Base
    instruction string
}

Try this on the Go Playground.

答案2

得分: 1

永远不要使用指向接口类型的指针,因为接口类型本身就是一个指针!

所以为了使代码正常工作,将*Queuable改为Queuable

type Node struct {
    value interface{}
    next  Queuable
}

// Next 获取下一个对象
func (n *Node) Next() Queuable {
    return n.next
}

// Job - 队列的作业
type Job struct {
    instruction string
    next        Queuable
}

然而,根据结构体的复杂性,你可以使用指针作为方法接收者。尽管如此,如果使用的结构体类型很简单,你可以定义方法来使用结构体的值,这样会在内存中分配一个新的地址。如果你将方法接收者作为指针,它将引用内存中已经占用的结构体地址。

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

这个规则的原因是指针方法可以修改接收者;在值上调用它们会导致方法接收到值的副本,因此任何修改都会被丢弃。因此,语言禁止了这个错误。

经验法则是,在整个接口实现中,最好始终坚持将方法定义为指针或值,以保持一致性。

英文:

Never use a pointer to an interface type, this is already a pointer!

So to make the code to work change the *Queuable as 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
}

However you can use a method receiver as pointer, depending of the struct complexity. Although if the used struct type is simple, you can define the method to use the struct value, this way allocating a new address in memory. If you use the method receiver as a pointer it will reference the address already occupied by the struct in memory.

The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.

This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.

The rule of thumb is that for consistency is better to stick either with method definition as a pointer or method definition as a value along the whole interface implementation.

huangapple
  • 本文由 发表于 2016年2月24日 15:27:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/35595810.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定