me := Person{}.
WithName("Elliot").
WithFavoriteColors([]string{"black", "blue"})
fmt.Printf("%+#vn", me)
// main.Person{name:"Elliot", favoriteColors:[]string{"black", "blue"}}
处理切片
目前为止仍然不是完美的,因为对于最爱颜色我们返回的是切片。由于切片通过引用传递,我们来看看这么一个稍不留神就会忽略的 bug:
func updateFavoriteColors(p Person) Person {
colors := p.FavoriteColors()
colors[0] = "red"
return p
}
func main() {
me := Person{}.
WithName("Elliot").
WithFavoriteColors([]string{"black", "blue"})
me2 := updateFavoriteColors(me)
fmt.Printf("%+#vn", me)
fmt.Printf("%+#vn", me2)
}
// main.Person{name:"Elliot", favoriteColors:[]string{"red", "blue"}}
// main.Person{name:"Elliot", favoriteColors:[]string{"red", "blue"}}
我们想要改变第一种颜色,但是连带地改变了 me 变量。因为在复杂应用程序中这不会导致代码无法运行,试图搜寻出这么个变化是相当烦人和耗时的。
解决方法之一是确保我们绝不通过索引赋值,而是永远都是分配一个新的切片:
func updateFavoriteColors(p Person) Person {
return p.WithFavoriteColors(append([]string{"red"}, p.FavoriteColors()[1:]...))
}
// main.Person{name:"Elliot", favoriteColors:[]string{"black", "blue"}}
// main.Person{name:"Elliot", favoriteColors:[]string{"red", "blue"}}
在我看来,这有点拙而且容易出错。更好的方式是一开始就不返回切片。拓展我们的 getter 和 wither 来仅对元素操作(而不是整个切片):
func (p Person) NumFavoriteColors() int {
return len(p.favoriteColors)
}
func (p Person) FavoriteColorAt(i int) string {
return p.favoriteColors[i]
}
func (p Person) WithFavoriteColorAt(i int, favoriteColor string) Person {
p.favoriteColors = append(p.favoriteColors[:i],
append([]string{favoriteColor}, p.favoriteColors[i+1:]...)...)
return p
}
译者注:上述代码是错误的,如果p.favoriteColors的容量大于i则会就地改变副本的favoriteColors,参见反例,稍作调整即可得到正确实现
现在我们就可以放心使用:
func updateFavoriteColors(p Person) Person {
return p.WithFavoriteColorAt(0, "red")
}
想要了解更多切片的妙用参见这篇牛逼的wiki:https://github.com/golang/go/wiki/SliceTricks
构造函数
某些情况下,我们会假设结构体的默认值是合理的。但是,强烈建议总是创建构造函数,一旦将来需要改变默认值时,我们只需要改动一个地方:
func NewPerson() Person {
return Person{}
}
你可以随心所欲地实例化 Person,但个人偏爱总是通过 setter 来执行状态变换从而保持代码一致性:









