问题背景
Go语言的面向对象在概念上与java大同小异,但是由于Go语言在给struct
添加method的时候需有一个显示的接收者(receiver),receiver可以是指针类型或是struct
的形参,二者到底有啥区别是在学习Go面向对象内容时最容易糊涂的地方。
问题描述
为了更好的阐述问题,首先撸上一段小代码:
//定义一个名为Controller的类
type Controller struct {
domain string
count int
}
//给Controller添加第一个成员方法firstFunc
func (c *Controller) firstFunc(domain string, count int) {
c.domain = domain
c.count = count
fmt.Println("firstFunc's domain is "+c.domain+" count is ", c.count)
}
//给Controller添加第二个成员方法secondFunc
func (c Controller) secondFunc(domain string, count int) {
c.domain = domain
c.count = count
fmt.Println("secondFunc's domain is "+c.domain+" count is ", c.count)
}
如上,在Go中定义类用struct
类型,在Controller类有两个field,分别为domian
和count
,还添加了两个方法,分别是firstFunc(domain string, count int)
和secondFunc(domain string, count int)
,不同的是firstFunc的receiver是Controller的指针而secondFunc传入的receiver是Controller的形参。那么问题来了不同类型的receiver具体会有哪些区别呢?下面将以笔者拙见进行总结归纳,分享之余也用于给自己记录。
问题解答
- 对类字段值修改结果不同
这个问题根据指针的意义就能理解,receiver为指针时相当于类的引用,*Controller.domain=domain
修改了Controller中domain字段的值,receiver为形参时,相当于Controller类的一个copy值,因此Controller.domain=domain
只是修改了Controller的一个副本的字段值,并非原Controller本身字段值。代码验证如下:
func main() {
//创建一个名为controller的Controller实体指针并初始化字段值
controller := &Controller{
domain: "www.baidu.com",
count: 1000,
}
//输出controller原始字段值
fmt.Println("conroller's original domain is <"+controller.domain+"> count is ", controller.count)
//调用firstFunc并查看controller字段值
controller.firstFunc("www.sohu.com", 500)
fmt.Println("firstFunc>conroller's domain is <"+controller.domain+"> count is ", controller.count)
//调用secondFunc并查看controller字段值
controller.secondFunc("www.qq.com", 800)
fmt.Println("secondFunc>conroller's domain is <"+controller.domain+"> count is ", controller.count)
}
运行结果如下:
- Controller实例与Controller实例指针包含方法不同
简单概括来讲,类的实例指针(也就是上一段代码中的controller)包含所有方法,即在以上代码中controller包含firstFunc和secondFunc两个方法,而实例只包含receiver为形参的方法,即若controller不是指针则firstFunc是不能够被成功调用的,因为其receiver为指针。为了能够更加直观的进行说明,下面用reflect反射机制进行验证:
func main() {
//初始化一个类指针controller1
controller1 := &Controller{
domain: "www.baidu.com",
count: 1000,
}
//初始化一个类controller2
controller2 := Controller{
domain: "www.sohu.com",
count: 800,
}
t1 := reflect.TypeOf(controller1)
//查看controller1的类型与包含的方法个数
fmt.Println("controller1's type is", t1.Kind(), "include", t1.NumMethod(), "methods")
//查看controller1的所有方法名称
for j := 0; j < t1.NumMethod(); j++ {
fmt.Println(++j,t1.Method(j).Name)
}
//查看controller2的类型与包含的方法个数
fmt.Println("controller2's type is", t2.Kind(), "include", t2.NumMethod(), "methods")
//查看controller2的所有方法名称
for j := 0; j < t2.NumMethod(); j++ {
fmt.Println(t2.Method(j).Name)
}
}
执行结果如下:
- 对接口实现的区别
接口通常会规定一些方法,实现了这些方法的类就相当于实现了相应接口,类方法的receiver是否为指针对接口实现的影响主要还在于类是否包含接口规定的所有方法。现定义一个接口:
type ControllerInter interface {
firstFunc(domain string, count int)
secondFunc(domain string, count int)
}
在上一问题验证代码中controller1因为包含了ControllerInter规定的所有方法,因此实现了该接口,而controller则没有实现。因此在判断类是否实现了某一接口时要特别注意其成员方法的receiver类型以及类的类型,尽管结果已经很清晰,但同样还是用代码加以验证:
func main() {
controller1 := &Controller{
domain: "www.baidu.com",
count: 1000,
}
controller2 := Controller{
domain: "www.sohu.com",
count: 800,
}
var controllerInter ControllerInter
controllerInter = controller1
controllerInter = controller2
}
执行结果如下:
声明一个名为controllerInter的接口变量,并将controller1和controller2一次复制给它,实现了接口的将顺利执行,没有实现的将会报错,如上图中controller2 does not implement ControllerInter接口。
结语
本文为作者原创,如有错误还请指正,轻拍!