跳至主要內容

接口

Mr.Liu大约 9 分钟Go

接口

接口介绍

语法

在Go中接口(interface)是一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,Golang中的接口不能包含任何变量。

在Go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态和高内聚低耦合的思想。

Go中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

Golang中每个接口由数个方法组成,接口的定义格式如下:

type 接口名 interface {
    方法名1 (参数列表1) 返回值列表1
    方法名2 (参数列表2) 返回值列表2
}

其中

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等,接口名最好突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名是可以省略

案例

演示:定义一个Usber接口让Phone 和 Camera结构体实现这个接口

首先我们定义一个Usber接口,接口里面就定义了两个方法

// 定义一个Usber接口
type Usber interface {
    start()
    stop()
}

然后我们在创建一个手机结构体

// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口

// 使用结构体来实现 接口
type Phone struct {
    Name string
}
// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) Start()  {
    fmt.Println(p.Name, "启动")
}
func (p Phone) Stop()  {
    fmt.Println(p.Name, "关闭")
}

然后我们在创建一个Phone的结构体,来实现这个接口

// 如果接口里面有方法的话,必须要通过结构体或自定义类型实现这个接口

// 使用结构体来实现 接口
type Phone struct {
    Name string
}

// 手机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Phone) start() {
    fmt.Println(p.Name, "启动")
}
func (p Phone) stop() {
    fmt.Println(p.Name, "关闭")
}
func main() {
    var phone Usber = Phone{
        "三星手机",
    }
    phone.start()
    phone.stop()
}

我们在创建一个Camera结构体

// 使用相机结构体来实现 接口
type Camera struct {
    Name string
}
// 相机要实现Usber接口的话,必须实现usb接口的所有方法
func (p Camera) start()  {
    fmt.Println(p.Name, "启动")
}
func (p Camera) stop()  {
    fmt.Println(p.Name, "关闭")
}
func main() {
    var camera Usber = Camera{
        "佳能",
    }
    camera.start()
    camera.stop()
}

我们创建一个电脑的结构体,电脑的结构体就是用于接收两个实现了Usber的结构体,然后让其工作

// 电脑
type Computer struct {

}

// 接收一个实现了Usber接口的 结构体
func (computer Computer) Startup(usb Usber)  {
    usb.start()
}

// 关闭
func (computer Computer) Shutdown (usb Usber)  {
    usb.stop()
}

最后我们在main中调用方法

func main() {
    var camera interfaceDemo.Camera = interfaceDemo.Camera{
        "佳能",
    }
    var phone interfaceDemo.Phone = interfaceDemo.Phone{
        "苹果",
    }

    var computer interfaceDemo.Computer = interfaceDemo.Computer{}
    computer.Startup(camera)
    computer.Startup(phone)
    computer.Shutdown(camera)
    computer.Shutdown(phone)
}

运行结果如下所示:

佳能 启动
苹果 启动
佳能 关闭
苹果 关闭

空接口

空接口(Empty Interface)是 Go 语言中的一个特殊类型,它不包含任何方法签名,因此可以表示任意类型的值。

// 空接口表示没有任何约束,任意的类型都可以实现空接口
type EmptyA interface {}

func main() {
    var a EmptyA
    var str = "你好golang"
    // 让字符串实现A接口
    a = str
    fmt.Println(a)
}

同时golang中空接口也可以直接当做类型来使用,可以表示任意类型。

var a interface{}
a = 20
a = "hello"
a = true

空接口可以作为函数的参数,使用空接口可以接收任意类型的函数参数

// 空接口作为函数参数
func show(a interface{}) {
    fmt.println(a)
}

map的值实现空接口

使用空接口实现可以保存任意值的字典

// 定义一个值为空接口类型
var studentInfo = make(map[string]interface{})
studentInfo["userName"] = "张三"
studentInfo["age"] = 15
studentInfo["isWork"] = true

slice切片实现空接口

// 定义一个空接口类型的切片
var slice = make([]interface{}, 4, 4)
slice[0] = "张三"
slice[1] = 1
slice[2] = true

类型断言

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。

如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)

其中:

  • x:表示类型为interface{}的变量
  • T:表示断言x可能是的类型

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败

package main

import (
    "fmt"
)

func main() {
    userInfo := make(map[string]interface{})
    userInfo["userName"] = "zhangsan"
    userInfo["age"] = 10
    userInfo["hobby"] = []string{"吃饭", "睡觉"}

    // 使用类型断言获取数组值
    if hobby, ok := userInfo["hobby"].([]string); ok {
        fmt.Println("Hobbies:", hobby) // 输出:Hobbies: [吃饭 睡觉]
    } else {
        fmt.Println("Hobby type assertion failed")
    }
}

在 Go 中,一个空接口中存储了一个特定类型的数据,但在没有进行类型断言之前,Go 编译器只能将其视为一个空接口。

或者我们可以定义一个能传入任意类型的方法

package main

import (
    "fmt"
)

func processInterfaceData(data interface{}) {
    if intValue, ok := data.(int); ok {
        fmt.Println("Received an integer:", intValue)
    } else if stringValue, ok := data.(string); ok {
        fmt.Println("Received a string:", stringValue)
    } else if stringSliceValue, ok := data.([]string); ok {
        fmt.Println("Received a slice of strings:", stringSliceValue)
    } else {
        fmt.Println("Received an unknown type")
    }
}

func main() {
    data1 := 42
    data2 := "Hello, World!"
    data3 := []string{"apple", "banana", "cherry"}

    processInterfaceData(data1) // 输出:Received an integer: 42
    processInterfaceData(data2) // 输出:Received a string: Hello, World!
    processInterfaceData(data3) // 输出:Received a slice of strings: [apple banana cherry]
}

上面的示例代码中,如果要断言多次,那么就需要写很多if,这个时候我们可以使用switch语句来实现:

注意: 类型.(type)​ 只能结合switch语句使用

package main

import (
    "fmt"
)

func processInterfaceData(data interface{}) {
    switch value := data.(type) {
    case int:
        fmt.Println("Received an integer:", value)
    case string:
        fmt.Println("Received a string:", value)
    case []string:
        fmt.Println("Received a slice of strings:", value)
    default:
        fmt.Println("Received an unknown type")
    }
}

func main() {
    data1 := 42
    data2 := "Hello, World!"
    data3 := []string{"apple", "banana", "cherry"}

    processInterfaceData(data1) // 输出:Received an integer: 42
    processInterfaceData(data2) // 输出:Received a string: Hello, World!
    processInterfaceData(data3) // 输出:Received a slice of strings: [apple banana cherry]
}

结构体接收者

值接收者

如果一个结构体(或类型)拥有使用值作为接收者的方法,那么无论你使用该结构体的值类型还是指针类型创建的实例,都可以将这些实例赋值给接口变量。

package main

import (
    "fmt"
)

// 定义一个接口
type Shape interface {
    Area() float64
}

// 定义一个矩形结构体
type Rectangle struct {
    Width  float64
    Height float64
}

// 值接收者方法:计算矩形的面积
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    // 使用值类型实例化矩形结构体
    rectValue := Rectangle{Width: 5, Height: 3}

    // 使用指针类型实例化矩形结构体
    rectPointer := &Rectangle{Width: 7, Height: 4}

    // 将值类型实例赋值给接口变量
    var shape1 Shape
    shape1 = rectValue
    fmt.Println("Area of rectValue:", shape1.Area()) // 输出:Area of rectValue: 15

    // 将指针类型实例赋值给接口变量
    var shape2 Shape
    shape2 = rectPointer
    fmt.Println("Area of rectPointer:", shape2.Area()) // 输出:Area of rectPointer: 28
}

结构体实现多个接口

一个结构体可以实现多个接口,这就意味着结构体需要提供满足每个接口所需的方法。

package main

import (
    "fmt"
)

// 定义一个形状接口
type Shape interface {
    Area() float64
}

// 定义一个可移动接口
type Movable interface {
    Move(dx, dy float64)
}

// 定义一个结构体:矩形
type Rectangle struct {
    Width  float64
    Height float64
}

// 值接收者方法:计算矩形的面积
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 指针接收者方法:移动矩形的位置
func (r *Rectangle) Move(dx, dy float64) {
    r.Width += dx
    r.Height += dy
}

func main() {
    rect := &Rectangle{Width: 5, Height: 3}

    // 将结构体实例赋值给接口变量
    var shape Shape = rect
    var movable Movable = rect

    // 使用接口调用方法
    fmt.Println("Area:", shape.Area()) // 输出:Area: 15
    movable.Move(2, 3)
    fmt.Printf("New Width: %.2f, New Height: %.2f\n", rect.Width, rect.Height)
    // 输出:New Width: 7.00, New Height: 6.00
}

接口嵌套

接口嵌套是指在一个接口中嵌入(嵌套)其他接口,从而组合它们的方法集合,形成一个新的更大的接口。通过这种方式,新接口将继承嵌套的其他接口的方法,并且可以通过一个接口变量来调用这些方法,实现更多的功能组合。

package main

import "fmt"

// 定义一个通用的写日志接口
type Logger interface {
    Log(message string)
}

// 定义一个数据库操作接口
type Database interface {
    Insert(data interface{})
    Query(query string) interface{}
}

// 定义一个高级接口,嵌套了 Logger 和 Database 接口
type AdvancedService interface {
    Logger
    Database
    ProcessData(data interface{})
}

// 定义一个结构体实现 Logger 接口
type ConsoleLogger struct{}

func (cl ConsoleLogger) Log(message string) {
    fmt.Println("Log:", message)
}

// 定义一个结构体实现 Database 接口
type InMemoryDatabase struct {
    data map[string]interface{}
}

func (imd InMemoryDatabase) Insert(data interface{}) {
    imd.data["key"] = data
}

func (imd InMemoryDatabase) Query(query string) interface{} {
    return imd.data["key"]
}

// 实现 AdvancedService 接口
type MyAdvancedService struct {
    Logger
    Database
}

func (mas MyAdvancedService) ProcessData(data interface{}) {
    mas.Log("Processing data...")
    mas.Insert(data)
}

func main() {
    logger := ConsoleLogger{}
    db := InMemoryDatabase{data: make(map[string]interface{})}

    // 使用 MyAdvancedService,结合了 Logger 和 Database 接口的功能
    advService := MyAdvancedService{
        Logger:   logger,
        Database: db,
    }

    advService.ProcessData("Hello, World!")

    result := advService.Query("key")
    fmt.Println("Query Result:", result)
}
// golang中空接口和类型断言
var userInfo = make(map[string]interface{})
userInfo["userName"] = "zhangsan"
userInfo["age"] = 10
userInfo["hobby"] = []string{"吃饭", "睡觉"}
fmt.Println(userInfo["userName"])
fmt.Println(userInfo["age"])
fmt.Println(userInfo["hobby"])
// 但是我们空接口如何获取数组中的值?发现 userInfo["hobby"][0]  这样做不行
// fmt.Println(userInfo["hobby"][0])

也就是我们的空接口,无法直接通过索引获取数组中的内容,因此这个时候就需要使用类型断言了

// 这个时候我们就可以使用类型断言了
hobbyValue,ok := userInfo["hobby"].([]string)
if ok {
    fmt.Println(hobbyValue[0])
}

通过类型断言返回来的值,我们就能够直接通过角标获取了。