Go

9.接口

2025-08-16  本文已影响0人  Surpassme

9.接口

    Go语言提供了一种称为接口(interface)的数据类型,用于表示一组行为规范,即定义一组未实现的函数声明。谁调用接口谁负责参照接口的方法负责实现它们。

    在Go语言中,使用组合实现对象特性的描述。对象内部使用结构体嵌套组合对象应该具有的特性,对外则通过暴露接口,让其他对象进行访问。Go语言中的接口设计是非侵入式的,接口编写者无需知道接口被哪些类型实现。而接口实现者只需要知道实现的是什么样子的接口,但也无需指明实现哪些接口。编译器知道最终编译时使用哪个类型实现哪个接口,或接口应该由谁来实现。接口是约束谁应该具有什么功能,实现某接口的方法,就具有该接口的功能

    在Go语言中,使用接口,通常遵循以下规则:

9.1 接口定义和实现

    接口是双方约定的一种合作协议,是一种类型,也是一种抽象结构。而接口实现指某一个结构体实现了接口声明的所有方法。而且一个结构体可以实现多个接口。接口定义的语法如下所示:

type name interface {
    method1(parameters) returnValue
    method2(parameters) returnValue
}

    接口方法只需要定义方法名称、参数名称和数据类型、返回的数据类型,无需在接口中编写方法的具体实现,方法的最终实现则实现者负责实现,一般是由结构体方法负责实现。示例代码如下所示:

package main

import "fmt"

// 定义接口
type Actions interface {
    // 无参,无返回值
    Walk()
    // 无参,有返回值
    Run() string
    // 有参,无返回值
    Shout(content string)
    // 有参,有返回值
    Rest(sleepTime int) string
}

// 定义结构体
type Dog struct {
    name string
}

// 实现接口
func (d *Dog) Walk() {
    fmt.Printf("%s is walking\n", d.name)
}

func (d *Dog) Run() string {
    return fmt.Sprintf("%s is running %f km", d.name, 10.08)
}

func (d *Dog) Shout(content string) {
    fmt.Printf("%s is shouting %s\n", d.name, content)
}

func (d *Dog) Rest(sleepTime int) string {
    return fmt.Sprintf("%s is reset %d minustes", d.name, sleepTime)
}

func main() {
    d := Dog{name: "小强"}
    d.Walk()
    fmt.Println(d.Rest(5))
    // 这里需要注意,只有d已经全部实现a的所有接口,才能这样赋值
    var a Actions = &d
    a.Walk()
    fmt.Println(a.Rest(5))
}

    运行结果如下所示:

小强 is walking
小强 is reset 5 minustes
小强 is walking
小强 is reset 5 minustes

9.2 接口嵌套

    结构体可以使用嵌套,接口同样也可以,通过接口嵌套,可以组成新的接口,并能形成简单的继承关系。示例如下所示:

type Reader interface {
    Read(p []byte) (n int,err error)
}

type Closer interface{
    Close() error
}

type ReadCloser interface{
    Reader
    Closer
} 

ReadCloser接口由Reader和Closer接口组合而成,也同时拥有Read和Close方法

    接口嵌套允许在不同接口定义相同的方法,但程序会将两个同名方法视为同一个方法,因此方法的参数和返回值必须一致。即不同接口中的同名方法参数返回值必须一致,否则程序执行会提示错误duplicate method Run (see details)

type Run interface {
    Run()
}

type Shout interface {
    Shout(content string) string
}

type Actions interface {
    Run
    Shout
    // 这样定义会报错
    // Run(name string) string
    Run()
}

接口嵌套通过多个接口组成一个新接口,使代码设计变得更加灵活,但为了降低各接口之间的方法命名冲突,各个接口和方法名称应尽量操持不同

9.3 空接口

    空接口实际上是指空接口类型,写成interface{}any。在空接口中,没有声明任何方法。因此,任何类型都无需要显式实现空接口的方法,因为任何数据类型都满足这个空接口的要求,因此,任何类型的值都可以看做是空接口类型。即空接口可以保存任意数据,也可以从空接口取出任意数据

为了使用方便,Go语言还为interface{}定义了一个别名any,即 type any = interface{}

    示例代码如下所示:

package main

import "fmt"

type EmptyInterface interface{}

func main() {
    // 定义空接口方式一:
    var a interface{} = "Surpass"
    fmt.Printf("a value:%+v type is %[1]T\n", a)
    a = 1.78
    fmt.Printf("a value:%+v type is %[1]T\n", a)

    // 定义空接口方式二:
    var b EmptyInterface = "Surmount"
    fmt.Printf("b value:%+v type is %[1]T\n", b)
    b = true
    fmt.Printf("b value:%+v type is %[1]T\n", b)
    b = []int{1, 2, 3}
    fmt.Printf("b value:%+v type is %[1]T\n", b)
}

    运行结果如下所示:

a value:Surpass type is string
a value:1.78 type is float64
b value:Surmount type is string
b value:true type is bool
b value:[1 2 3] type is []int

    这里使用空接口主要是指切片、Map的元素和结构体的字段可以为任意数据。

package main

import (
    "fmt"
)

func main() {
    // 切片元素为空接口类型
    s := []interface{}{"Surpass", "Shanghai", 1.89, false}
    for _, v := range s {
        fmt.Printf("slice v value is:%v,type is %[1]T\n", v)
    }

    m := map[string]interface{}{
        "name":     "Surpass",
        "location": "Shanghai",
        "height":   1.89,
    }
    for _, v := range m {
        fmt.Printf("map v value is:%v,type is %[1]T\n", v)
    }

    ss := struct {
        Name     interface{}
        Location interface{}
        Height   interface{}
        Gender   interface{}
    }{
        Name:     "Surpass",
        Location: "Shanghai",
        Height:   1.89,
        Gender:   1,
    }
    fmt.Printf("name type:%T Location type: %T Height type: %T Gender type:%T\n", ss.Name, ss.Location, ss.Height, ss.Gender)
}

    运行结果如下所示:

slice v value is:Surpass,type is string
slice v value is:Shanghai,type is string
slice v value is:1.89,type is float64
slice v value is:false,type is bool
map v value is:Surpass,type is string
map v value is:Shanghai,type is string
map v value is:1.89,type is float64
name type:string Location type: string Height type: float64 Gender type:int
package main

import "fmt"

func EmptyInterfaceFunc(data any) any {
    fmt.Printf("parameter valeu: %v, type is %[1]T\n", data)
    return data
}

func main() {
    a := []interface{}{"Surpass", "Shanghai", 1.89, false}
    EmptyInterfaceFunc(a)
    EmptyInterfaceFunc(123)
    EmptyInterfaceFunc(map[string]any{"name": "Surpass", "age": 28})
    EmptyInterfaceFunc("Surpass")
    EmptyInterfaceFunc(false)
}

    运行结果如下所示:

parameter valeu: [Surpass Shanghai 1.89 false], type is []interface {}
parameter valeu: 123, type is int
parameter valeu: map[age:28 name:Surpass], type is map[string]interface {}
parameter valeu: Surpass, type is string
parameter valeu: false, type is bool

9.4 接口类型断言

    接口类型断言可以将接口转换为另外一种接口,也可以将接口转换为另外的类型。其语法格式如下所示:

t:=x.(T)

    若断言失败,即x没有实现T的接口方法,则panic。若为这种形式t,ok:=x.(T),则通过ok判断是否为T类型接口。示例代码如下所示:

9.4.1 基础类型接口断言

package main

import "fmt"

func main() {
    var a interface{} = "Surpass"
    var b interface{} = 100
    if tb, ok := b.(int); ok {
        fmt.Printf("tb转换成功,值为%v", tb)
    } else {
        fmt.Printf("tb转换失败")
    }
    ta := a.(int) //转换失败,出现panic
    fmt.Printf("ta的值为:%v", ta)
}

    代码运行结果如下所示:

tb转换成功,值为100
panic: interface conversion: interface {} is string, not int

9.4.2 结构体接口断言

package main

import "fmt"

// 定义接口
type Animal interface {
    Run() string
    Shout(content string)
    Reset(time int) string
}

// 定义结构体
type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

func (d *Dog) Run() string {
    return fmt.Sprintf("%s is running\n", d.Name)
}

func (d *Dog) Shout(content string) {
    fmt.Printf("%s is shout %s\n", d.Name, content)
}

func (d *Dog) Reset(time int) string {
    return fmt.Sprintf("%s is reset %d minustes\n", d.Name, time)
}

func (c *Cat) Run() string {
    return fmt.Sprintf("%s is running\n", c.Name)
}

func main() {
    var a Animal
    d := &Dog{Name: "小强"}
    a = d
    if _, ok := a.(Animal); ok {
        fmt.Printf("%#v转换成功", a)
    } else {
        fmt.Printf("%#v转换失败", a)
    }

    // 因为Cat未全部实现接口Animal的方法,以下代码会出现报错
    // c := &Cat{Name: "小花"}
    // a = c
    // if _, ok := a.(Animal); ok {
    //  fmt.Printf("%#v转换成功", a)
    // } else {
    //  fmt.Printf("%#v转换失败", a)
    // }
}

    代码运行结果如下所示:

&main.Dog{Name:"小强"}转换成功

9.4.3 switch断言

    可以使用switch断言来对接口做多种类型的断言,基本语法如下所示:

switch i.(type){
    case nil:
       ...
    case string:
       ...
    case int:
       ...
    default:
       ...
}
// 或

switch v:=i.(type){
    case nil:
       ...
    case string:
       ...
    case int:
       ...
    default:
       ...
}

    示例代码如下所示:

package main

import "fmt"

func main() {
    var a interface{} = "Surpass"
    switch a.(type) {
    case string:
        fmt.Printf("%v type is string\n", a)
    case int:
        fmt.Printf("%v type is int\n", a)
    case bool:
        fmt.Printf("%v type is bool\n", a)
    case nil:
        fmt.Printf("%v type is unknown\n", a)
    }
    // 在switch中使用转换后的结果
    switch v := a.(type) {
    case string:
        fmt.Printf("string: %+v", v)
    case int:
        fmt.Printf("int: %+v", v)
    case bool:
        fmt.Printf("bool: %+v", v)
    case nil:
        fmt.Printf("nil: %+v", v)
    }
}

    运行结果如下所示:

Surpass type is string
string: Surpass

9.5 鸭子类型

    在现实生活中,我们也许都见过真正的鸭子,它拥有生命、会游泳,但现实生活中也各类型鸭子模型或玩具,例如:大黄鸭。从人们认知的角度来看,它不是一只真正的鸭子,不仅没有生命,也不会游泳,所以也不具备现实鸭子的本能,但从鸭子类型角度来看,它就是一只鸭子。实际上,在编程语言的角度来看,只要走起来像鸭子,或在某一些方面像鸭子,那么它就是一只鸭子。即鸭子类型只关心事物的外部行为而不关心内部结构

    在接口中,其方法必须要与结构体进行绑定;而每次使用时,都需要创建接口变量、创建结构体实例化变量、结构体与接口绑定等步骤,在使用上会造成很多不便。如果将接口与结构体的绑定的过程以函数实现,只要传入结构体实例化变量就能自动执行接口方法,是不是要方便很多。示例代码如下所示:

package main

import (
    "fmt"
)

type Actions interface {
    Shout(content string)
}

type Duck struct {
    Name string
}

type Chicken struct {
    Name string
}

type Bird struct {
    Name string
}

func (d *Duck) Shout(content string) {
    fmt.Printf("%s %s\n", d.Name, content)
}

func (c *Chicken) Shout(content string) {
    fmt.Printf("%s %s\n", c.Name, content)
}

func (b *Bird) Shout(content string) {
    fmt.Printf("%s %s\n", b.Name, content)
}

func Shout(a Actions, content string) {
    a.Shout(content)
}

func main() {
    d := &Duck{Name: "我是一只鸭子"}
    c := &Chicken{Name: "我是一只小鸡"}
    b := &Bird{Name: "我是一只小鸟"}
    Shout(d, "嘎嘎")
    Shout(c, "咯咯")
    Shout(b, "啾啾")
}

    代码运行结果如下所示:

我是一只鸭子 嘎嘎
我是一只小鸡 咯咯
我是一只小鸟 啾啾
上一篇 下一篇

猜你喜欢

热点阅读