如何在Go中使用指针作为集合类型的内部数据结构来实现接口?

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

How to use pointers to interface in go as internal data structure for collection-like types?

问题

TLDR; 我需要在我的Node中将指针用作内部Data对象的接口,例如*Comparable,作为Tree的一部分。Data应符合指向只有一个方法LessComparable接口的指针,该方法用于比较两个Comparable接口类型的对象。

如果我在NodeData中只使用Comparable作为类型,而不使用指针,一切都正常工作。然而,当我切换到*Comparable后,编译器会给出奇怪的错误,代码无法编译。

使用Comparable的代码:

package main

type Comparable interface {
	Less(than Comparable) bool
}

type Node struct {
	Data Comparable
}

func NewNode(data Comparable) *Node {
	return &Node{Data: data}
}

type Tree struct {
	root *Node
}

func NewTree() *Tree {
	return &Tree{}
}

func (t *Tree) insert(data Comparable) {
	if t.root == nil || t.root.Data.Less(data) {
		t.root = NewNode(data)
	}
}

type Row struct {
	Row []string
}

func NewRow(row []string) *Row {
	return &Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
	return r.Row[0] < other.(Row).Row[0]
}

func main() {
	t := NewTree()
	t.insert(*NewRow([]string{"123"}))
	fmt.Printf("%v\n", t.root.Data.(Row).Row)
}

测试:

package main

import (
	"reflect"
	"testing"
)

func TestInsert(t *testing.T) {
	d := []string{"42"}
	tree := NewTree()
	tree.insert(*NewRow(d))
	if !reflect.DeepEqual(tree.root.Data.(Row).Row, d) {
		t.Error("returned elements are not matching")
	}
}

func TestInsert2x(t *testing.T) {
	d1 := []string{"42"}
	d2 := []string{"99"}
	tree := NewTree()
	tree.insert(*NewRow(d1))
	tree.insert(*NewRow(d2))
	if !reflect.DeepEqual(tree.root.Data.(Row).Row, d2) {
		t.Error("returned elements are not matching")
	}
}

然而,当我将Comparable转换为*Comparable时,一切都崩溃了,根本原因是类似于*Row不等于*Comparable的编译器错误:

对于行return r.Row[0] < other.(*Row).Row[0]

无效的类型断言:other.(Row)(左侧为非接口类型Comparable)

对于行if t.root == nil || t.root.Data.Less(data) {

未解析的引用'Less'

package main

type Comparable interface {
	Less(than *Comparable) bool
}

type Node struct {
	Data *Comparable
}

func NewNode(data *Comparable) *Node {
	return &Node{Data: data}
}

type Tree struct {
	root *Node
}

func NewTree() *Tree {
	return &Tree{}
}

func (t *Tree) insert(data *Comparable) {
	if t.root == nil || t.root.Data.Less(data) {
		t.root = NewNode(data)
	}
}

type Row struct {
	Row []string
}

func NewRow(row []string) *Row {
	return &Row{Row: row}
}

func (r *Row) Less(other *Comparable) bool {
	return r.Row[0] < other.(*Row).Row[0]
}

func main() {
	t := NewTree()
	t.insert(NewRow([]string{"123"}))
	fmt.Printf("%v\n", t.root.Data.(*Row).Row)
}

测试:

package main

import (
	"reflect"
	"testing"
)

func TestInsert(t *testing.T) {
	d := []string{"42"}
	tree := NewTree()
	tree.insert(NewRow(d))
	if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d) {
		t.Error("returned elements are not matching")
	}
}

func TestInsert2x(t *testing.T) {
	d1 := []string{"42"}
	d2 := []string{"99"}
	tree := NewTree()
	tree.insert(NewRow(d1))
	tree.insert(NewRow(d2))
	if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d2) {
		t.Error("returned elements are not matching")
	}
}

问题是如何在NodeData中使用*Comparable作为类型,以便上述代码可以编译。我尝试了一些使用interface{}类型和显式转换的不太好的选项。我不喜欢这种方法,因为它非常不安全。

你可以在我的 GitHub 存储库中查看所有代码部分。

英文:

TLDR; I need to use pointers to interface e.g. *Comparable as an internal Data's object in my Node as a part of Tree. Data should conform to pointer to Comparable interface that has only one method Less which compares two objects of Comparable interface type.

If I am using just Comparable as type for Data in Node, without pointer, everything works fine. However after I switch to *Comparable compiler gives strange errors and code won't compile

Code with Comparable:

package main

type Comparable interface {
	Less(than Comparable) bool
}

type Node struct {
	Data Comparable
}

func NewNode(data Comparable) *Node {
	return &amp;Node{Data: data} }

type Tree struct {
	root *Node
}

func NewTree() *Tree {
	return &amp;Tree{}
}

func (t *Tree) insert(data Comparable) {
	if t.root == nil || t.root.Data.Less(data) {
		t.root = NewNode(data)
	}
}

type Row struct {
	Row []string
}

func NewRow(row[] string) *Row {
	return &amp;Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
	return r.Row[0] &lt; other.(Row).Row[0]
}

func main() {
	t := NewTree()
	t.insert(*NewRow([]string{&quot;123&quot;}))
	fmt.Printf(&quot;%v\n&quot;, t.root.Data.(Row).Row)
}

tests:

package main

import (
	&quot;reflect&quot;
	&quot;testing&quot;
)

func TestInsert(t *testing.T) {
	d := []string{&quot;42&quot;}
	tree := NewTree()
	tree.insert(*NewRow(d))
	if !reflect.DeepEqual(tree.root.Data.(Row).Row, d) {
		t.Error(&quot;returned elements are not matching&quot;)
	}
}

func TestInsert2x(t *testing.T) {
	d1 := []string{&quot;42&quot;}
	d2 := []string{&quot;99&quot;}
	tree := NewTree()
	tree.insert(*NewRow(d1))
	tree.insert(*NewRow(d2))
	if !reflect.DeepEqual(tree.root.Data.(Row).Row, d2) {
		t.Error(&quot;returned elements are not matching&quot;)
	}
}

However when I convert Comparable to *Comparable, everything breaks and the root cause is something like *Row is not equal to *Comparable compiler error:

for line return r.Row[0] &lt; other.(*Row).Row[0]
> Invalid type assertion: other.(*Row) (non-interface type *Comparable on left)

and for line if t.root == nil || t.root.Data.Less(data) {
> Unresolved reference 'Less'

package main

type Comparable interface {
	Less(than *Comparable) bool
}

type Node struct {
	Data *Comparable
}

func NewNode(data *Comparable) *Node {
	return &amp;Node{Data: data}
}

type Tree struct {
	root *Node
}

func NewTree() *Tree {
	return &amp;Tree{}
}

func (t *Tree) insert(data *Comparable) {
	if t.root == nil || t.root.Data.Less(data) {
		t.root = NewNode(data)
	}
}

type Row struct {
	Row []string
}

func NewRow(row[] string) *Row {
	return &amp;Row{Row: row}
}

func (r *Row) Less(other *Comparable) bool {
	return r.Row[0] &lt; other.(*Row).Row[0]
}

func main() {
	t := NewTree()
	t.insert(NewRow([]string{&quot;123&quot;}))
	fmt.Printf(&quot;%v\n&quot;, t.root.Data.(*Row).Row)
}

tests:

package main

import (
	&quot;reflect&quot;
	&quot;testing&quot;
)

func TestInsert(t *testing.T) {
	d := []string{&quot;42&quot;}
	tree := NewTree()
	tree.insert(NewRow(d))
	if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d) {
		t.Error(&quot;returned elements are not matching&quot;)
	}
}

func TestInsert2x(t *testing.T) {
	d1 := []string{&quot;42&quot;}
	d2 := []string{&quot;99&quot;}
	tree := NewTree()
	tree.insert(NewRow(d1))
	tree.insert(NewRow(d2))
	if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d2) {
		t.Error(&quot;returned elements are not matching&quot;)
	}
}

The question is how to use *Comparable as type of Data in Node so that code above compiles. I tried a couple ugly options with interface{} type for Data and explicit casts everywhere. This approach I didn't like as it is very unsafe.

You can check all code parts in my github repo

答案1

得分: 2

接口可以接受结构体和结构体指针。

你可以将指向Row(*Row)的指针传递给Tree.insert(data Comparable),通过将第一个Tree.insert(data Comparable)函数定义与第二个main()函数调用结合起来。

这是一个完整的运行示例,也可以在Go Playground上查看:https://play.golang.org/p/Gh_RT3R-Fy0。

package main

import (
    "fmt"
)

type Comparable interface {
    Less(than Comparable) bool
}

type Node struct {
    Data Comparable
}

func NewNode(data Comparable) *Node {
    return &Node{Data: data}
}

type Tree struct {
    root *Node
}

func NewTree() *Tree {
    return &Tree{}
}

func (t *Tree) insert(data Comparable) {
    if t.root == nil || t.root.Data.Less(data) {
        t.root = NewNode(data)
    }
}

type Row struct {
    Row []string
}

func NewRow(row []string) *Row {
    return &Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
    return r.Row[0] < other.(*Row).Row[0]
}

func main() {
    t := NewTree()
    t.insert(NewRow([]string{"123"}))
    fmt.Printf("%v\n", t.root.Data.(*Row).Row)
}

话虽如此,这段代码很脆弱,因为Row.Less函数访问了Comparable的底层类型,并假设数组具有0索引值(长度>0)。

func (r Row) Less(other Comparable) bool {
    return r.Row[0] < other.(*Row).Row[0]
}

最好的做法是在调用接口函数时,消除对Comparable底层类型的任何依赖。你可以通过增加Comparable接口和Row.Less定义来实现这一点。在这种方法中,不需要在Row结构定义中进行类型转换。

在Go Playground上查看:https://play.golang.org/p/8-71-pEn-zK

type Comparable interface {
    Less(than Comparable) bool
    CompareValue() string
}

func (r Row) CompareValue() string {
    if len(r.Row) == 0 {
        return ""
    }
    return r.Row[0]
}

func (r Row) Less(other Comparable) bool {
    return r.CompareValue() < other.CompareValue()
}

有关指向接口的指针的更多信息,请参阅Go FAQ中关于使用指向接口的指针的部分:

https://golang.org/doc/faq#pointer_to_interface

何时应该使用指向接口的指针?

几乎从不需要。指向接口值的指针只在涉及将接口值的类型伪装为延迟评估的罕见且棘手的情况下出现。

将指向接口值的指针传递给期望接口的函数是一个常见的错误。编译器会对此错误进行投诉,但情况仍然可能令人困惑,因为有时需要使用指针来满足接口。关键在于,尽管指向具体类型的指针可以满足接口,但指向接口的指针除外,它永远不能满足接口。

[...]

唯一的例外是任何值,甚至是指向接口的指针,都可以分配给空接口类型(interface{})的变量。即便如此,如果该值是指向接口的指针,几乎肯定是一个错误;结果可能会令人困惑。

英文:

Interfaces can accept both structs and pointers to structs.

You can pass in a pointer to Row (*Row) to Tree.insert(data Comparable), by combining your 1st Tree.insert(data Comparable) function definition with your 2nd main() function calls.

Here's a full running example, also on Go Playground: https://play.golang.org/p/Gh_RT3R-Fy0 .

package main

import (
    &quot;fmt&quot;
)

type Comparable interface {
	Less(than Comparable) bool
}

type Node struct {
	Data Comparable
}

func NewNode(data Comparable) *Node {
	return &amp;Node{Data: data}
}

type Tree struct {
	root *Node
}

func NewTree() *Tree {
	return &amp;Tree{}
}

func (t *Tree) insert(data Comparable) {
	if t.root == nil || t.root.Data.Less(data) {
		t.root = NewNode(data)
	}
}

type Row struct {
	Row []string
}

func NewRow(row []string) *Row {
	return &amp;Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
	return r.Row[0] &lt; other.(*Row).Row[0]
}

func main() {
	t := NewTree()
	t.insert(NewRow([]string{&quot;123&quot;}))
	fmt.Printf(&quot;%v\n&quot;, t.root.Data.(*Row).Row)
}

That being said, this is brittle code since the Row.Less function accesses the underlying type of Comparable and assumes the array has at a 0 index value (length > 0).

func (r Row) Less(other Comparable) bool {
	return r.Row[0] &lt; other.(*Row).Row[0]
}

It's much better to remove any dependency on the underlying type of Comparable when interface functions are called. You can do this augmenting the Comparable interface and Row.Less definition as follows. In this approach, there's no need to cast in the Row struct definition.

On Go Playground: https://play.golang.org/p/8-71-pEn-zK

type Comparable interface {
	Less(than Comparable) bool
	CompareValue() string
}

func (r Row) CompareValue() string {
	if len(r.Row) == 0 {
		return &quot;&quot;
	}
	return r.Row[0]
}

func (r Row) Less(other Comparable) bool {
	return r.CompareValue() &lt; other.CompareValue()
}

For more information on pointer to interfaces, see the Go FAQ on using pointers to interfaces:

https://golang.org/doc/faq#pointer_to_interface

> When should I use a pointer to an interface?
>
> Almost never. Pointers to interface values arise only in rare, tricky situations involving disguising an interface value's type for delayed evaluation.
>
> It is a common mistake to pass a pointer to an interface value to a function expecting an interface. The compiler will complain about this error but the situation can still be confusing, because sometimes a pointer is necessary to satisfy an interface. The insight is that although a pointer to a concrete type can satisfy an interface, with one exception a pointer to an interface can never satisfy an interface.
>
> [...]
>
> The one exception is that any value, even a pointer to an interface, can be assigned to a variable of empty interface type (interface{}). Even so, it's almost certainly a mistake if the value is a pointer to an interface; the result can be confusing.

huangapple
  • 本文由 发表于 2021年7月24日 23:31:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/68511424.html
匿名

发表评论

匿名网友

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

确定