💜 想真正擅长编程? 从一门课程开始,100% 免费

《笔记》第二十三篇 Go的接口说明

第23讲 Go的接口说明

本讲基于视频实际讲解内容整理


一、本讲概述

本讲讲解Go语言中**接口(Interface)**的概念和用法。接口是Go语言实现多态的核心机制,老师从接口的定义出发,对比了Go接口与Java接口的异同,并演示了结构体方法、接口实现、嵌套结构体等相关知识。


二、什么是接口

2.1 接口的概念

老师在课程中介绍了接口的基本概念:

接口是一种类型,它定义了一组方法的集合。接口只声明方法,不实现方法,具体的实现由结构体来完成。

Go
// 定义一个接口
type Animal interface {
    Eat()        // 声明方法,不实现
    Sleep()
}

2.2 接口与Java的对比

老师将Go的接口与Java的接口做了对比:

特性 Java接口 Go接口
关键字 interface interface
实现方式 显式使用 implements 隐式实现(鸭子类型)
方法声明 有方法签名 有方法签名
多实现 可以实现多个接口 自动满足多个接口

老师指出:Go的接口和Java差不多,但Go不需要显式声明"实现了某个接口",只要结构体实现了接口中定义的所有方法,就自动满足该接口。这就是Go的隐式接口特性。


三、结构体与方法

3.1 定义结构体

在讲接口之前,老师先回顾了结构体的定义:

Go
type Dog struct {
    Name string
    Age  int
}

3.2 为结构体定义方法

Go中为结构体定义方法使用特殊的接收者语法:

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中实现接口不需要显式声明,只要结构体实现了接口中所有方法,就自动实现了该接口:

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 接口类型变量

接口类型的变量可以持有任何实现了该接口的值:

Go
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
}

老师强调:通过接口,我们可以用同一个接口变量调用不同结构体的方法,这就是多态。接口能够适应变化的参数,因为我们不绑定某个具体的类型。


五、接口作为函数参数

接口最常见的用途是作为函数的参数类型,这样可以接收任何实现了该接口的值:

Go
// 函数接收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 嵌套结构体

Go
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 嵌套的意义

老师指出,结构体中什么都可以放,包括其他结构体:

结构体里面可以放各种类型,包括另一个结构体。这就是结构体的嵌套,通过嵌套可以实现代码的复用和组合。

Go
// 嵌套多个结构体
type Company struct {
    Name    string
    Address Address    // 嵌套地址
    CEO     Person     // 嵌套人员
}

七、封装与可见性

7.1 首字母大小写控制可见性

老师再次强调了Go中通过首字母大小写来控制可见性的规则:

首字母 可见性 说明
大写 公有(导出) 其他包可以访问
小写 私有(未导出) 仅当前包可以访问
Go
type User struct {
    Name    string  // 大写:包外可访问
    Age     int     // 大写:包外可访问
    phone   string  // 小写:仅包内可访问
}

7.2 封装的概念

老师提到,通过大小写控制可见性,就实现了Go中的封装:

通过大写字母来控制包外是否可见。包可见的字段就是大写,小写的字段在包外是不可见的。这就是Go的封装方式,和Java中的private/public类似,但更加简洁。


八、接口的完整示例

Go
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

十、课后练习

  1. 定义一个 Vehicle 接口,包含 Start() 和 Stop() 方法,然后编写 Car 和 Bike 结构体实现该接口
  2. 编写函数 MakeVehicleStart(v Vehicle),接收接口类型参数,分别传入 Car 和 Bike 测试
  3. 定义一个 Calculator 接口,包含 Add(a, b int) int 方法,编写结构体实现它
  4. 尝试使用指针接收者和值接收者分别定义方法,观察区别
  5. 定义一个嵌套结构体(如 Student 中包含 Address),并访问嵌套字段
  6. 将结构体中某些字段改为小写开头,尝试从另一个包中访问,观察报错
  7. 思考:Go的隐式接口实现相比Java的显式implements有什么优缺点?