概述
方法是面向对象编程 (OOP) 的一个特性,在 C++/Java 语言中方法是类函数,go做为函数式编程语言,通过特有技能支持相似的功能,所以说go也支持面向对象编程特性。
go 方法本质也是函数,相比普通函数稍有区别,方法必须与具体类型绑定,且无法独立运行,只能通过类型实例执行,函数是一等公民,方法是二等公民。方法很像面向对象的类方法,但又有区别,方法更加松散,耦合性更低。类方法是定义在对象内部,go方法更像是一种属性扩展,在不修改类型定义的情况下可扩展方法,比如可为外部引入的结构体增加方法,这在Java
、C#等面向对象语言是不允许的。
基本使用
方法可以绑定到任意类型,但是实际情况总是与结构体绑定,两者结合可以模拟面向对象特性,当然仅是模拟,文章主要使用结构体演示。
定义类型
type Person struct { Age int Name string }
为结构体定义方法
func (p *Person) sayHi() { // 为结构体定义了一个方法 fmt.Println("hi, I'm ", p.Name) }
相比普通函数定义稍有区别,在func
关键字和函数名之间增加一个接收器,或称为接收者、连接器等,如上接收器是Person
类型指针。与接收器参数匹配的类型,就增加了一个新方法,目前观察这是go的独创。
方法方必须依实例,无法独立执行。与面型对象中方法一样,先有对象,才能调用方法。
sayHi() // error undefined: sayHi
必须通过结构体实例调用,会自动把实例传递给方法的接收器,类似Java
的this
、Python
的self
,隐式传递第一个参数。
p1 := Person{Name: "tom"} p1.sayHi() // hi, I'm tom
为简单理解,编译后伪代码如下
func sayHi(self) { // self是体结构体指针 fmt.Println("hi, I'm ", self.Name) }
接收器参数类型,除非有明确需求,否则都应该使用指针。同样的问题,使用值类型本质是每次调用,都传入复制的新实例。
func (p Person) sayHi() { p.name = "tony" fmt.Println("hi, I'm ", p.Name) // tony } func main() { p1 := Person{Name: "tom"} p1.sayHi() // 复制新结构体传递给sayHi fmt.Println(p1.Name) // tom }
语法层面没有限制,允许为任何类型创建方法,包括基础数据类型。但是有一个限制,方法和类型定义必须在同一个包,为基础数据类型、或引入第三方类型定义方法需要一些变通,先使用type
定义类型,在扩展方法。
为基本数据类型扩展方法
type Integer int func (m *Integer) Value() { fmt.Println(*m) }
使用方法
var m Integer = 10 m.Value() // 10
假如Person是从第三方引入的类型,为其扩展新方法
type MyPerson Person func (m *MyPerson) getName() { return Person(*m).Name // 先强制转为Person类型,再读取Name属性 }
使用方法
p1 := MyPerson{Name: "tom"} p1.getName() p1.sayHi // err
sayHi
是Person结构体的方法,无法通过MyPerson类型访问,也不支持常规的继承
更优雅是使用组合的方式(继承模式),Go
语言不支持传统面向对象中的继承特性,而是以自己特有的组合方式支持了方法的继承,通过在结构体内置匿名的成员来实现继承
type MyPerson struct { City string Person // 匿名属性 } func (m *MyPerson) getCity() { return m.City }
使用方法
p1 := MyPerson{City: "shanghai"} p1.getCity() // shanghai p1.sayHi() // ok,调用继承方法
被继承的对象称为基础类型,属性、方法都可以被继承,注意继承方法在调用时连接器接收的对象是原始对象,如上案例中sayHi
接收的是Person
对象,而非MyPerson
对象,这种展开是编译期完成的, 并没有运行时代价。这与C++、C#、Java主流面向对象语言不同,子类方法在运行时动态绑定到对象,因此基类某些方法看到的 this 可能不是基类类型对应的对象,这个特性会导致基类方法运行的不确定性。
方法是由函数演变而来,只是将函数第一个参数移动到函数名前面而已,方法是特殊的函数。两者特性几乎一样,比如都是值传递、都不支持重载,甚至通过方法表达式可以将方法还原为普通函数
sayHi := (*Person).sayHi // 方法转换为函数 p := &Person{Age: 1, Name: "xk"} // 创建结构体 sayHi(p) // 调用转换后的函数
转换为普通函数后,将接收器转换为函数的第一个参数,调用时候需要显示传递参数。
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!