第23讲 Go的接口说明
本讲基于视频实际讲解内容整理
一、本讲概述
本讲讲解Go语言中**接口(Interface)**的概念和用法。接口是Go语言实现多态的核心机制,老师从接口的定义出发,对比了Go接口与Java接口的异同,并演示了结构体方法、接口实现、嵌套结构体等相关知识。
二、什么是接口
2.1 接口的概念
老师在课程中介绍了接口的基本概念:
接口是一种类型,它定义了一组方法的集合。接口只声明方法,不实现方法,具体的实现由结构体来完成。
// 定义一个接口
type Animal interface {
Eat() // 声明方法,不实现
Sleep()
}
2.2 接口与Java的对比
老师将Go的接口与Java的接口做了对比:
| 特性 | Java接口 | Go接口 |
|---|---|---|
| 关键字 | interface |
interface |
| 实现方式 | 显式使用 implements |
隐式实现(鸭子类型) |
| 方法声明 | 有方法签名 | 有方法签名 |
| 多实现 | 可以实现多个接口 | 自动满足多个接口 |
老师指出:Go的接口和Java差不多,但Go不需要显式声明"实现了某个接口",只要结构体实现了接口中定义的所有方法,就自动满足该接口。这就是Go的隐式接口特性。
三、结构体与方法
3.1 定义结构体
在讲接口之前,老师先回顾了结构体的定义:
type Dog struct {
Name string
Age int
}
3.2 为结构体定义方法
Go中为结构体定义方法使用特殊的接收者语法:
// 值接收者方法
func (d Dog) Eat() {
fmt.Println(d.Name + " is eating")
}
// 指针接收者方法
func (d *Dog) Sleep() {
fmt.Println(d.Name + " is sleeping")
}
3.3 值接收者 vs 指针接收者
老师讲解了值接收者和指针接收者的区别:
| 接收者类型 | 语法 | 特点 |
|---|---|---|
| 值接收者 | func (d Dog) Method() |
操作副本,不影响原对象 |
| 指针接收者 | func (d *Dog) Method() |
操作原对象,可修改字段 |
老师提醒:当结构体比较大时,建议使用指针接收者,避免每次调用都复制整个结构体。指针直接指向内存空间,效率更高。
四、接口的实现
4.1 隐式实现
Go中实现接口不需要显式声明,只要结构体实现了接口中所有方法,就自动实现了该接口:
package main
import "fmt"
// 定义接口
type Animal interface {
Eat()
Sleep()
}
// 定义结构体
type Dog struct {
Name string
}
// 实现Eat方法
func (d Dog) Eat() {
fmt.Println(d.Name + " is eating")
}
// 实现Sleep方法
func (d Dog) Sleep() {
fmt.Println(d.Name + " is sleeping")
}
func main() {
// Dog实现了Animal接口的所有方法,自动满足Animal接口
var a Animal = Dog{Name: "旺财"}
a.Eat()
a.Sleep()
}
关键点:Go的接口是隐式实现的,不需要像Java那样写
implements Animal。这就是Go的鸭子类型——"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
4.2 接口类型变量
接口类型的变量可以持有任何实现了该接口的值:
type Cat struct {
Name string
}
func (c Cat) Eat() {
fmt.Println(c.Name + " is eating")
}
func (c Cat) Sleep() {
fmt.Println(c.Name + " is sleeping")
}
func main() {
var a Animal
a = Dog{Name: "旺财"}
a.Eat() // 旺财 is eating
a = Cat{Name: "咪咪"}
a.Eat() // 咪咪 is eating
}
老师强调:通过接口,我们可以用同一个接口变量调用不同结构体的方法,这就是多态。接口能够适应变化的参数,因为我们不绑定某个具体的类型。
五、接口作为函数参数
接口最常见的用途是作为函数的参数类型,这样可以接收任何实现了该接口的值:
// 函数接收Animal接口类型的参数
func MakeAnimalEat(a Animal) {
a.Eat()
}
func main() {
dog := Dog{Name: "旺财"}
cat := Cat{Name: "咪咪"}
MakeAnimalEat(dog) // 旺财 is eating
MakeAnimalEat(cat) // 咪咪 is eating
}
老师说明:在函数参数中使用接口类型,可以让我们传入任何实现了该接口的结构体,实现了代码的灵活性和扩展性。
六、结构体的嵌套
老师在课程中演示了结构体的嵌套(Embedding),即在一个结构体中包含另一个结构体:
6.1 嵌套结构体
type Address struct {
City string
Street string
}
type Person struct {
Name string
Age int
Address Address // 嵌套Address结构体
}
func main() {
p := Person{
Name: "张三",
Age: 30,
Address: Address{
City: "北京",
Street: "朝阳区",
},
}
fmt.Println(p.Name) // 张三
fmt.Println(p.Address.City) // 北京
}
6.2 嵌套的意义
老师指出,结构体中什么都可以放,包括其他结构体:
结构体里面可以放各种类型,包括另一个结构体。这就是结构体的嵌套,通过嵌套可以实现代码的复用和组合。
// 嵌套多个结构体
type Company struct {
Name string
Address Address // 嵌套地址
CEO Person // 嵌套人员
}
七、封装与可见性
7.1 首字母大小写控制可见性
老师再次强调了Go中通过首字母大小写来控制可见性的规则:
| 首字母 | 可见性 | 说明 |
|---|---|---|
| 大写 | 公有(导出) | 其他包可以访问 |
| 小写 | 私有(未导出) | 仅当前包可以访问 |
type User struct {
Name string // 大写:包外可访问
Age int // 大写:包外可访问
phone string // 小写:仅包内可访问
}
7.2 封装的概念
老师提到,通过大小写控制可见性,就实现了Go中的封装:
通过大写字母来控制包外是否可见。包可见的字段就是大写,小写的字段在包外是不可见的。这就是Go的封装方式,和Java中的private/public类似,但更加简洁。
八、接口的完整示例
package main
import "fmt"
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 圆形结构体
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.Radius
}
// 矩形结构体
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 使用接口作为参数
func PrintShapeInfo(s Shape) {
fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 4, Height: 6}
PrintShapeInfo(circle) // 面积: 78.50, 周长: 31.40
PrintShapeInfo(rectangle) // 面积: 24.00, 周长: 20.00
}
九、本讲小结
| 知识点 | 要点 |
|---|---|
| 接口定义 | type 接口名 interface { 方法签名... } |
| 隐式实现 | 不需要implements,实现所有方法即满足接口 |
| 鸭子类型 | "如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子" |
| 多态 | 接口变量可持有不同实现,调用对应方法 |
| 值接收者 | func (d Dog) Method(),操作副本 |
| 指针接收者 | func (d *Dog) Method(),操作原对象,可修改 |
| 接口作参数 | 函数参数使用接口类型,增加灵活性 |
| 结构体嵌套 | 结构体中可包含其他结构体 |
| 封装 | 首字母大写=公有,小写=私有 |
| 与Java对比 | Go接口不需要显式implements |
十、课后练习
- 定义一个
Vehicle接口,包含Start()和Stop()方法,然后编写Car和Bike结构体实现该接口 - 编写函数
MakeVehicleStart(v Vehicle),接收接口类型参数,分别传入 Car 和 Bike 测试 - 定义一个
Calculator接口,包含Add(a, b int) int方法,编写结构体实现它 - 尝试使用指针接收者和值接收者分别定义方法,观察区别
- 定义一个嵌套结构体(如
Student中包含Address),并访问嵌套字段 - 将结构体中某些字段改为小写开头,尝试从另一个包中访问,观察报错
- 思考:Go的隐式接口实现相比Java的显式implements有什么优缺点?