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. 一致性。如果该类型的æŸäº›æ–¹æ³•å¿…须具有指针接收器,则其余方法也应如此,因此无论使用何ç§ç±»åž‹ï¼Œæ–¹æ³•é›†éƒ½æ˜¯ä¸€è‡´çš„。对我æ¥è¯´ï¼Œè¿™æ„味ç€å¦‚果类型应该用作指针(例如,频ç¹ä¿®æ”¹ï¼‰ï¼Œå®ƒåº”该使用方法集æ¥æ ‡è®°ã€‚它并ä¸æ„味ç€ä¸€ç§ç±»åž‹åªèƒ½æœ‰æŒ‡é’ˆæ–¹æ³•æˆ–å过æ¥ã€‚