Mat*_* Mc 90 struct interface go
我很熟悉在Go中,接口定义功能而不是数据.您将一组方法放入接口,但是您无法指定实现该接口的任何字段.
例如:
// Interface
type Giver interface {
Give() int64
}
// One implementation
type FiveGiver struct {}
func (fg *FiveGiver) Give() int64 {
return 5
}
// Another implementation
type VarGiver struct {
number int64
}
func (vg *VarGiver) Give() int64 {
return vg.number
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以使用接口及其实现:
// A function that uses the interface
func GetSomething(aGiver Giver) {
fmt.Println("The Giver gives: ", aGiver.Give())
}
// Bring it all together
func main() {
fg := &FiveGiver{}
vg := &VarGiver{3}
GetSomething(fg)
GetSomething(vg)
}
/*
Resulting output:
5
3
*/
Run Code Online (Sandbox Code Playgroud)
现在,你不能做的是这样的:
type Person interface {
Name string
Age int64
}
type Bob struct implements Person { // Not Go syntax!
...
}
func PrintName(aPerson Person) {
fmt.Println("Person's name is: ", aPerson.Name)
}
func main() {
b := &Bob{"Bob", 23}
PrintName(b)
}
Run Code Online (Sandbox Code Playgroud)
然而,在使用接口和嵌入式结构之后,我发现了一种方法,在时尚之后:
type PersonProvider interface {
GetPerson() *Person
}
type Person struct {
Name string
Age int64
}
func (p *Person) GetPerson() *Person {
return p
}
type Bob struct {
FavoriteNumber int64
Person
}
Run Code Online (Sandbox Code Playgroud)
由于嵌入式结构,Bob拥有Person所拥有的一切.它还实现了PersonProvider接口,因此我们可以将Bob传递给旨在使用该接口的函数.
func DoBirthday(pp PersonProvider) {
pers := pp.GetPerson()
pers.Age += 1
}
func SayHi(pp PersonProvider) {
fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}
func main() {
b := &Bob{
5,
Person{"Bob", 23},
}
DoBirthday(b)
SayHi(b)
fmt.Printf("You're %v years old now!", b.Age)
}
Run Code Online (Sandbox Code Playgroud)
这是一个Go Playground,演示了上面的代码.
使用这种方法,我可以创建一个定义数据而不是行为的接口,只需嵌入该数据就可以通过任何结构实现.您可以定义与嵌入数据明确交互的函数,并且不知道外部结构的本质.并且在编译时检查所有内容!(你可以搞砸了,我能看到的唯一方法,将嵌入界面PersonProvider中Bob,而不是一个具体的Person,它会编译并在运行时失败.)
现在,这是我的问题:这是一个巧妙的技巧,还是我应该以不同的方式做到这一点?
two*_*two 49
这绝对是一个巧妙的技巧,只要你很酷,可以访问这些字段作为API的一部分.我要考虑的替代方案是保留可嵌入的结构/ interface设置,但是根据getter和setter来定义接口.
隐藏getter和setter背后的属性为您提供了一些额外的灵活性,可以在以后进行向后兼容的更改.假设你有一天想改变Person存储而不仅仅是一个"名称"字段,而是第一个/中间/最后一个/前缀; 如果你有方法Name() string和SetName(string),您可以保留现有的用户Person界面快乐,同时增加新的更细粒度的方法.或者,您可能希望能够在数据库支持的对象有未保存的更改时将其标记为"脏"; 当数据更新全部通过SetFoo()方法时,你可以这样做.
所以:使用getter/setter,你可以在保持兼容API的同时更改struct字段,并在属性get/sets周围添加逻辑,因为没有人可以p.Name = "bob"不经过你的代码.
当您的类型执行更复杂的操作时,这种灵活性更具相关性.如果你有一个PersonCollection,它可能是由内部的支持sql.Rows,一个[]*Person,一个[]uint数据库ID,或什么的.使用正确的界面,您可以保护呼叫者免受照顾,io.Reader使网络连接和文件看起来相似.
一个特定的事情:interfaceGo中的s具有特殊属性,您可以在不导入定义它的包的情况下实现它; 这可以帮助您避免循环导入.如果你的接口返回一个*Person,而不是只是字符串或其他什么,都PersonProviders必须导入Person定义的包.这可能是好的,甚至是不可避免的; 这只是了解的结果.
总而言之,没有Go惯例,你必须隐藏所有数据.(这是与C++相比的一个受欢迎的区别.)stdlib做的事情就像让你http.Server用你的配置初始化一样,并承诺零bytes.Buffer可用.做这样的事情很好,事实上,如果更具体的数据暴露版本有效,我认为你不必做过早的抽象.这只是意识到权衡.
如果我正确理解您想将一个结构字段填充到另一个字段中。我的观点是不要使用接口来扩展。您可以通过下一个方法轻松完成。
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
Citizenship string
}
type Bob struct {
SSN string
Person
}
func main() {
bob := &Bob{}
bob.Name = "Bob"
bob.Age = 15
bob.Citizenship = "US"
bob.SSN = "BobSecret"
fmt.Printf("%+v", bob)
}
Run Code Online (Sandbox Code Playgroud)
https://play.golang.org/p/aBJ5fq3uXtt
注意Person在Bob声明。这将使包含的结构字段Bob直接在结构中可用一些语法糖。