写在前面
在上一篇文章《Golang入门学习之结构体(struct)》当中,我们学习了Golang当中结构体(struct)的知识点,接下来我们将学习Golang当中的方法(method)。
方法的定义
在Golang当中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此,方法是一种特殊的函数。这里的接收者可以(几乎,接收者类型不能是一个接口类型或指针类型)任何类型,不仅仅是结构体类型,也就意味着,几乎任何类型都可以方法,甚至是函数类型,或者是int、bool等的别名类型。
我们可以这样理解:一个类型(比如说是结构体)加上它的方法就等价于面向对象语言当中的一个类。
方法的定义格式
1 | go复制代码func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... } |
recv
就像是面向对象语言中的 this
或 self
,但是 Golang 中并没有这两个关键字。随个人喜好,你可以使用 this
或 self
作为 receiver 的名字。
注意点
在 Golang 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。请看下面这个例子:
我们在src/go_code/method/model/immortal.go
当中定义了一个修仙者类型
1 | go复制代码package model |
然后,我们在src/go_code/method/model/immortal_method.go
当中定义immortal
类型的方法
1 | go复制代码package model |
再然后,我们再main包当中使用它
1 | go复制代码package main |
输出:
1 | 复制代码韩立 |
函数与方法的区别
函数和方法都是一段可复用的代码段。他们的区别在于函数是面向过程的,方法是面向对象。从调用上来看,函数通过函数名进行调用,而方法则通过与实例关联的变量进行调用。
1 | go复制代码// 函数调用 |
再看Golang当中,方法由接收者类型、方法名、形参列表、返回值列表和方法体五部分构成,并且接收者必须有一个显式的名字,这个名字必须在方法中被使用。而且,接收者类型(receiver_type)必须在和方法同样的包中被声明。
Golang中方法的其他特性
在Golang当中,接收者类型关联的方法不写在类型结构里面(面向对象语言Java的方法是在类当中进行定义的)。因此,在Golang当中方法与接收者类型的耦合更加地宽松,也就是说,数据(字段)与其对应的行为是相互独立。
接收者类型可以是一个值而不是类型的指针吗?
接收者类型可以是一个值而不是类型的指针吗?答案是可以的。但是,基于性能方面的考虑,我并不建议大家这样做。因为接收者是作为值传递给对应的方法的,这相当于将实例的值拷贝传递给方法,这并不是一件划算的买卖。请看下面的例子,接收者完全可以是实例的值。
1 | go复制代码// 修仙者等级 |
输出:
1 | go复制代码练气九层 |
注意:
指针方法和值方法都可以在指针或非指针上被调用。如下面程序所示,类型 Level
在值上有一个方法 GetLevel()
,在指针上有一个方法 SetLevel()
,但是可以看到两个方法都可以在两种类型的变量上被调用。
1 | go复制代码package model |
1 | go复制代码package main |
输出:
1 | 复制代码晋级之前: 练气九层 |
方法和未导出字段
在上面的例子当中,level
类型的字段对包外部而言是不可见的(可以理解为面向对象语言当中的private
属性)。因此如果在main
包当中直接通过选择器进行访问的话,将会报错。这是,我们可以通过面向对象语言一个众所周知的技术来完成:提供 getter 和 setter 方法。在Golang当中,对于 setter 方法使用 Set 前缀,对于 getter 方法只使用成员名。
关于并发访问对象
对象的字段(属性)不应该由 2 个或 2 个以上的不同线程在同一时间去改变。如果在程序发生这种情况,为了安全并发访问,可以使用包 sync
中的方法(比如加个互斥锁)。但是这并不是一个推荐的选项(之后我们将会学习通过 goroutines 和 channels 去探索一种新的方式)。请看下面的例子
src/go_code/method/model/level_lock.go
1 | go复制代码package model |
src/go_code/struct/main/level_lock.go
1 | go复制代码package main |
内嵌类型的方法和继承
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型。这个机制提供了一种简单的方式来模拟经典面向对象语言中的子类和继承相关的效果。因为一个结构体可以嵌入多个匿名类型,所以实际上我们可以有一个简单版本的多重继承
。
在model包当中定义一个immortal2
类型,并让其内嵌一个匿名类型level
src/go_code/method/model/anonymous_type.go
:
1 | go复制代码package model |
src/go_code/method/model/level.go
:
1 | go复制代码package model |
src/go_code/method/model/lingen.go
:
1 | go复制代码package model |
在main包当中导入并使用
1 | go复制代码package main |
输出:
1 | go复制代码境界: 练气大圆满 |
Go 的类型和方法和其他面向对象语言对比
在如 C++、Java、C# 和 Python这样的面向对象语言中,方法在类的上下文中被定义和继承:在一个对象上调用方法时,运行时会检测类以及它的超类中是否有此方法的定义,如果没有会导致异常发生。
在 Golang 中,这样的继承层次是完全没必要的:如果方法在此类型定义了,就可以调用它,和其他类型上是否存在这个方法没有关系。在这个意义上,Golang具有更大的灵活性。
Golang不需要一个显式的类定义,如同 Java和C++等那样,相反地,“类”是通过提供一组作用于一个共同类型的方法集加类型本身来隐式定义的。类型可以是结构体或者任何用户自定义类型。
总结
在Golang中,类=类型+与之关联的方法集。
在 Golang 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 组件编程(Component Programming)。
相比于类继承,Go 的接口(后面将会详细讲解)提供了更强大、却更简单的多态行为。
写在后面
关于Golang中方法的学习就写到这了。本文当中涉及到的例子可以点击此处下载。如果我的学习笔记能够给你带来帮助,还请多多点赞鼓励。文章如有错漏之处还请各位小伙伴帮忙斧正。
本文转载自: 掘金