前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang Interface 详解(上)

Golang Interface 详解(上)

作者头像
Se7en258
发布2023-05-18 15:10:14
2020
发布2023-05-18 15:10:14
举报
文章被收录于专栏:Se7en的架构笔记Se7en的架构笔记

  独特的“非侵入式”接口设计是 Go 语言的一亮点。接口使得 Go 这种静态型语言有了动态型语言的特性,提供了非常大的灵活性。Go 语言的成功,接口功不可没。

Golang 与"鸭子类型"的关系

Ducking Typing ,鸭子类型,是动态编程语言的一种对象推断策略,它主要关注于对象如何被使用,而不是对象类型的本身。Go 语言作为一门静态语言,它通过 interface 的方式完美支持鸭子类型。

  Go 语言作为一门现代静态语言,吸取了“前辈”们的经验和教训,有很大的后发优势。它引入了动态语言的便利,同时又会进行静态语言的类型检查。Go 不要求类型显示的实现的某个接口,只要求实现了接口相关方法即可。

  让我们看一个例子:

代码语言:javascript
复制
package main

import "fmt"

// Greeting 定义一个接口
type Greeting interface {
 sayHello()
}

// 使用接口作为函数参数
func sayHello(g Greeting) {
 g.sayHello()
}

// Go 定义结构体,并且实现该接口
type Go struct{}

func (g Go) sayHello() {
 fmt.Println("hello,i am go")
}

type Java struct{}

func (j Java) sayHello() {
 fmt.Println("hello, i am java")
}

func main() {
 golang := Go{}
 java := Java{}
 sayHello(golang)
 sayHello(java)
}

  在 main 函数中,调用 sayHello 函数时,传入结构体的实例化对象,它们并没有显示的实现接口。实际上,编译器在调用 sayHello()时,会隐式的将 golang,java 对象转换为 Greeting 类型,这其实也是静态语言的类型检查功能。

值接收者和指针接受者的区别

  • 方法 :

  方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。

  在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

  总而言之,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。

  让我们看一个例子:

代码语言:javascript
复制
package main

import "fmt"

type Person struct {
 age int
}

func (p Person) howOld() int {
 return p.age
}

func (p *Person) growUp() {
 p.age += 1
}

func main() {
 // tom 是值类型
 tom := Person{age: 18}

 // 值类型 调用接收者也是值类型的方法
 fmt.Println(tom.howOld())

 // 值类型 调用接收者是指针类型的方法
 tom.growUp()
 fmt.Println(tom.howOld())

 // ----------------------

 // steven是指针类型
 steven := &Person{age: 100}

 // 指针类型 调用接收者是值类型的方法
 fmt.Println(steven.howOld())

 // 指针类型 调用接收者也是指针类型的方法
 steven.growUp()
 fmt.Println(steven.howOld())
}

  实际上,当类型和方法的接受者类型不同时,其实是编译器替我们“负重前行”了。

  • 值接收者和指针接受者 :

  上文说过,无论接受者是值类型还是指针类型,都可以通过值类型和指针类型调用,这就是语法糖起了作用。

  结论就是,实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法。而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法。   让我们来看一个例子:

代码语言:javascript
复制
package main

import "fmt"

type coder interface {
 code()
 debug()
}

type Gopher struct {
 language string
}

func (p Gopher) code() {
 fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
 fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
 var c coder = &Gopher{"Go"}
 c.code()
 c.debug()
}

  上述代码运行结果如下

  从表面上看,*Gopher 类型并没有实现 code 方法,但是因为 Gopher 类型实现了 code 方法,所以让 *Gopher 类型自动拥有了 code 方法。

  但是我们把main函数的第一条语句换一下

代码语言:javascript
复制
func main() {
 var c coder = Gopher{"Go"}
 c.code()
 c.debug()
}

  运行一下,报如下错误

  看出这两处代码的差别了吗?第一次是将 &Gopher 赋给了 coder;第二次则是将 Gopher 赋给了 coder。

  第二次报错是说,Gopher 没有实现 coder。很明显了吧,因为 Gopher 类型并没有实现 debug 方法。

  其实这个地方隐藏着一个"玄机",这段错误的官方解释为:“接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响。”

  通俗点讲就是:“当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。”

  其实关于这个解释,我个人觉得太繁琐,让我们仔细看一下main函数里的代码,我们可以发现,结构是赋值给了interface,所以我总结了一下:   1: 类型 *T 赋值给interface的可调用方法集包含接受者为 *T 或 T 的所有方法。   2: 类型T赋值给interface的可调用方法集包含接受者为T的所有方法。

  • 值接收者和指针接收者如何选择:

  如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。

总的来说如何选择就是以下两点: 1.设计不可变对象,用值接收。 2.其它用指针。 Tips:遇事不决用指针!!!

加餐:Golang如何实现多态

  严格意义上讲,Golang并不是一门面向对象语言,它没有其它语言所谓的面向对象三要素,但是Golang通过了interface优雅的实现了面向对象的特性。

  多态上一种运行期的行为,有以下几个特点。 1.一种类型具有多种类型的能力。 2.允许不同的对象对同一消息做出灵活的反应。 3.以一种通用的方式对待使用的对象。 4.非动态语言必须通过继承和接口的方式来实现。

  让我们来看一个例子:

代码语言:javascript
复制
package main

import "fmt"

type Person interface {
 job()
 growUp()
}

type Student struct {
 age int
}

type Programmer struct {
 age int
}

func (p Student) job() {
 fmt.Println("I am a student.")
 return
}

func (p *Student) growUp() {
 p.age += 1
 return
}

func (p Programmer) job() {
 fmt.Println("I am a programmer.")
 return
}

func (p Programmer) growUp() {
 // 程序员老得太快 ^_^
 p.age += 10
 return
}

func whatJob(p Person) {
 p.job()
}

func growUp(p Person) {
 p.growUp()
}

func main() {
 tom := Student{age: 18}
 whatJob(&tom)

 growUp(&tom)
 fmt.Println(tom)

 sam := Programmer{age: 100}
 whatJob(sam)

 growUp(sam)
 fmt.Println(sam)
}

  首先定义了一个接口Person,该接口包含两个方法 job() 和 growUp()。然后定义了两个结构体,其都实现了了person接口。

  main函数里,生成Student和Programmer 的对象,再将它们分别传入到函数whatJob和growUp。函数中,直接调用接口函数,实际执行的时候是看最终传入的实体类型是什么,调用的是实体类型实现的函数。于是,不同对象针对同一消息就有多种表现,多态就实现了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Se7en的架构笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Golang 与"鸭子类型"的关系
  • 值接收者和指针接受者的区别
  • 加餐:Golang如何实现多态
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档