9.接口

9.接口

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

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

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

  • 接口命名习惯在接口名称后面添加er做为后缀
  • 参数列表、返回值列表可以不写
  • 如果要想在包外使用接口,接口名称首字母需要大写
  • 接口方法若想在包外使用,首字母也需要大写
  • 接口中的方法应该设计合理,不要太多

9.1 接口定义和实现

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

type name interface {
	method1(parameters) returnValue
	method2(parameters) returnValue
}
  • name: 接口名称
  • methodX:接口方法名称
  • 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和结构体使用空接口

    这里使用空接口主要是指切片、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: 转换的目标类型
  • 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, "啾啾")
}

    代码运行结果如下所示:

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

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

posted @ 2025-08-17 23:58  Surpassme  阅读(9)  评论(0)    收藏  举报