Stringer方法需要值

时间:2018-01-17 08:40:20

标签: go methods interface

The Go FAQ answers a question关于在方法中选择by-value与by-pointer接收器的定义。该答案中的一个陈述是:

  

如果该类型的某些方法必须具有指针接收器,则其余方法也应如此,因此无论使用何种类型,方法集都是一致的。

这意味着如果我的数据类型有一些变异数据的方法,因此需要按指针接收器,我应该使用by-pointer接收器来为该数据类型定义的所有方法。

另一方面,"fmt"包调用String()界面按值定义的Stringer方法。如果使用接收器by-pointer定义String()方法,则当关联数据类型作为fmt.Println(或其他fmt格式化方法)的参数给出时,不会调用它。这使得除了使用接收器按值实现String()方法之外别无选择。

如同常见问题解答建议的那样,如何满足fmt接口的Stringer要求,那么如何与按值与按指针的选择保持一致?

编辑:

为了强调我提到的问题的本质,考虑一种情况,其中一个数据类型具有一组用接收器按值定义的方法(包括String())。然后,人们希望添加一个改变该数据类型的附加方法 - 所以他用接收器指针定义它,并且(为了保持一致,根据FAQ答案),他还更新了要使用的数据类型的所有其他方法。 - 指针接收器。此更改对使用此数据类型的方法的任何代码没有任何影响 - 但是对于fmt格式化函数的调用(现在需要将指针传递给变量而不是其值,如更改之前)。因此,一致性要求仅在fmt的上下文中存在问题。根据接收器类型调整一个变量为fmt.Println(或类似函数)的方式的需要打破了轻松重构一个包的能力。

2 个答案:

答案 0 :(得分:3)

如果使用指针接收器定义方法,则应使用并传递指针值而不是非指针值。这样做的传递值确实实现了Stringer,而fmt包没有问题“检测”并调用您的String()方法。

示例:

type Person struct {
    Name string
}

func (p *Person) String() string {
    return fmt.Sprintf("Person[%s]", p.Name)
}

func main() {
    p := &Person{Name: "Bob"}
    fmt.Println(p)
}

输出(在Go Playground上尝试):

Person[Bob]

如果要将Person类型的值传递给fmt.Println()而不是类型为*Person的指针,是的,确实不会调用Person.String()。但是如果Person的所有方法都有指针接收器,那就是强指示,你应该使用类型及其值作为指针(除非你不打算使用它的方法)。

是的,您必须知道是否必须使用Person*Person。处理它。如果你想编写正确有效的程序,你必须知道的不仅仅是使用指针还是非指针值,我不知道为什么这对你来说很重要。如果您不知道,请查找它,如果您是懒惰的,请使用指针作为方法集(类型)指针值包含指针和非指针接收器的方法。

Person的作者也可以为您提供NewPerson()工厂函数,您可以依赖它来返回正确类型的值(例如Person如果方法有值接收器,并且*Person如果方法有指针接收器,那么你就不必知道使用哪个。

回答稍后将一个带指针接收器的方法添加到以前只有带有值接收器的方法的类型:

是的,正如您在问题中所描述的那样,可能不会破坏现有代码,但继续使用非指针值可能无法从后来添加的带指针接收器的方法中获益。

我们可能会问:这是一个问题吗?使用类型时,您刚刚添加的新方法不存在。所以原始代码没有假设它的存在。所以它应该不是问题。

第二个考虑:类型只有具有值接收器的方法,因此可以很容易地假设通过它们的使用,值是不可变,因为具有值接收器的方法不能改变该值。使用该类型的代码可能已建立在此基础上,假设它没有被其方法更改,因此从多个goroutine中使用它可能正确地省略了某些同步。

所以我认为将指针接收器的新方法添加到以前只有具有值接收器的方法的类型不应该是“不透明的”,添加此新方法的人有责任修改此类型的使用“切换”到指针并确保代码保持安全和正确,或者处理非指针值不具备这种新方法的事实。

<强>提示:

如果某个类型将来可能有mutator方法,您应该开始使用带有指针接收器的方法创建它。这样做可以避免以后必须完成上述过程。

另一个提示可能是完全隐藏类型,只发布接口。这样做,这种类型的用户不必知道接口是否包装指针,这无关紧要。它们接收接口值,并调用接口的方法。包作者负责处理正确的方法接收器,并返回实现接口的适当类型。客户看不到这一点,他们不依赖于此。他们所看到和使用的只是界面。

答案 1 :(得分:2)

  

为了强调我提到的问题的本质,考虑一种情况,其中一个数据类型具有一组用接收器按值定义的方法(包括String())。然后,人们希望添加一个改变该数据类型的附加方法 - 所以他用接收器指针定义它,并且(为了保持一致,根据FAQ答案),他还更新了要使用的数据类型的所有其他方法。 - 指针接收器。此更改对使用此数据类型的方法的任何代码没有影响 - 但是对于fmt格式化函数的调用(现在需要将指针传递给变量而不是其值,如更改之前)。

事实并非如此。它的所有interface和一些类型断言也会受到影响 - 这就是fmt受到影响的原因。例如:

package main

import (
    "fmt"
)

type I interface {
    String() string
}

func (t t) String() string { return "" }

func (p *p) String() string { return "" }

type t struct{}
type p struct{}

func S(i I) {}

func main() {
    fmt.Println("Hello, playground")
    T := t{}
    P := p{}
    _ = P
    S(T)
    //S(P) //fail
}

要从root中理解这一点,您应该知道指针方法和值方法与基础不同。但是,为方便起见,就像省略;一样,golang编译器使用没有指针的指针方法查找案例并将其更改回来。

如此处所述:https://tour.golang.org/methods/6

回到原始问题:指针方法的一致性。如果你仔细阅读faq,你会发现它是考虑使用值或指针方法的最后一部分。您可以在container/heap中找到标准lib示例中的反例:

// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
    // We want Pop to give us the highest, not lowest, priority so we use greater than here.
    return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
    pq[i], pq[j] = pq[j], pq[i]
    pq[i].index = i
    pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
    n := len(*pq)
    item := x.(*Item)
    item.index = n
    *pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    item.index = -1 // for safety
    *pq = old[0 : n-1]
    return item
}

// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
    item.value = value
    item.priority = priority
    heap.Fix(pq, item.index)
}

事实上,正如FAQ所说,要确定是否使用指针方法,请按顺序考虑以下因素:

  1. 该方法是否需要修改接收器?如果是,请使用指针。如果没有,应该有充分的理由,否则会造成混乱。
  2. 效率。如果接收器很大,例如一个大的结构,使用指针接收器会便宜得多。但是,效率并不容易讨论。如果您认为这是一个问题,请在此之前对其进行简介和/或基准测试。
  3. 一致性。如果该类型的某些方法必须具有指针接收器,则其余方法也应如此,因此无论使用何种类型,方法集都是一致的。对我来说,这意味着如果类型应该用作指针(例如,频繁修改),它应该使用方法集来标记。它并不意味着一种类型只能有指针方法或反过来。