Go中的Receiver和Method Sets
Go中没有class关键字,如果想将一组方法关联起来,除了将其放到同一个package下面以外,还有一种方式就是为方法指定receiver(receiver为某个类型),此时方法就与receiver类型关联起来了,有点类似于面向对象中类型方法的概念(但又很不同)。这篇文章介绍了指定receiver的两种方式以及相关的method sets概念。
两种类型的receiver
对于方法,可以指定两种类型的 receiver:T和*T。假设有一个struct类型User,则为方法SetName()指定Receiver时,可以使用User或者*User:
type User struct { Name string } func (x User) SetName(name string) { fmt.Printf("x1: %p\n", &x) x.Name = name } func (x *User) SetName2(name string) { fmt.Printf("x2: %p\n", x) x.Name = name } func (x User) GetName() string { return x.Name }
一个实例胜过前言万语千言,运行一下就可以看出它们的差别:
func main() { test1() } func test1() { u1 := User{Name: "alex"} fmt.Printf("u1: %p\n", &u1) u1.SetName("jimmy") fmt.Println("u1.Name:", u1.Name) // alex u2 := &User{Name: "alex"} // u2为指针 fmt.Printf("u2: %p\n", u2) u2.SetName2("jimmy") fmt.Println("u2.Name:", u2.Name) // jimmy }
输出如下:
# go run main.go u1: 0xc0000601c0 x1: 0xc0000601d0 u1.Name: alex u2: 0xc0000601f0 x2: 0xc0000601f0 u2.Name: jimmy
当receiver为User时,传入SetName()的x是一个新的User结构;当reciver为*User时,传入SetName2()的仍是test1()中创建的u2。
看到这里,你可能觉得这是显而易见的,因为SetName2()接受和传入的都是指针类型。
但实际上,即使将u1转换为指针,而u2不为指针,结果也是一样的:
func test2() { u1 := &User{Name: "alex"} // u1为指针 u1.SetName("jimmy") fmt.Println("u1.Name:", u1.Name) // alex u2 := User{Name: "alex"} u2.SetName2("jimmy") fmt.Println("u2.Name:", u2.Name) // jimmy }
可见,receiver参数和常规参数是有很大不同的,这里的关系取决于方法定义时参数是T还是*T,而不取决于传入的参数类型,编译器做了自动的转换。
除此以外,如果是常规方法,形参和实参的类型要完全一致,下面这个例子就能说明这个问题:
型参:方法定义时指定的参数;实参:调用方式时实际传入的参数。
func test3() { u1 := User{Name: "alex"} // cannot use u (type User) as type *User in argument to ShowName ShowName2(u1) //cannot use u2 (type *User) as type User in argument to ShowName u2 := &User{Name: "jimmy"} ShowName(u2) } func ShowName(u User) { fmt.Println("ShowName:", u.Name) } func ShowName2(u *User) { fmt.Println("ShowName:", u.Name) }
上面的代码是无法编译的,因为参数类型不对。
method sets 和 interface
不同的receiver类型除了产生上面的影响以外,也影响了类型的method sets,进而影响了类型所实现的接口。关于method sets,官方定义如下:
A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).
对于类型 T 来说,它的method set就是所有receiver类型为T的方法。而对于它所对应的指针类型 *T,method set则为所有receiver类型为T和*T的方法。
对于我们的例子来说,User的method set为SetName()、GetName(),*User的method set为SetName()、SetName2()、GetName()。
这个method sets有什么用呢?它影响了类型实现的接口,在Go中不需要显示指定类型实现的接口,只要它的method sets包含了所有interface中定义的方法就可以了。
添加这样的一个interface和接受这个interface的方法:
type IUser interface { SetName(string) SetName2(string) GetName() string } func ShowUser(user IUser) { fmt.Println(user.GetName()) }
因为u1的method sets不包含SetName2(),也就没能实现IUser接口,而函数ShowUser()接受的是IUser接口,所以编译器就报错了(u2则没有这个问题):
func test4() { u1 := User{Name: "alex"} u1.SetName2("jimmy") ShowUser(u1) // 编译器报错 u2 := &User{Name: "alex"} u2.SetName2("jimmy") ShowUser(u2) }
由此,应该已经明白了receiver、method sets的相关概念和区别。
感谢阅读,希望这篇文章能给你带来帮助!