Golang接口使用教程详解

目录
  • 前言
  • 一、概述
  • 二、接口类型
    • 2.1 接口的定义
    • 2.2 实现接口的条件
    • 2.3 为什么需要接口
    • 2.4 接口类型变量
  • 三、值接收者和指针接收者
    • 3.1 值接收者实现接口
    • 3.2 指针接收者实现接口
  • 四、类型与接口的关系
    • 4.1 一个类型实现多个接口
    • 4.2 多种类型实现同一接口
  • 五、接口嵌套
  • 六、空接口
  • 七、类型断言
  • 总结

前言

go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、implements关键词。

一、概述

在 Go 语言中接口包含两种含义:它既是方法的集合, 同时还是一种类型。在Go 语言中是隐式实现的,意思就是对于一个具体的类型,不需要声明它实现了哪些接口,只需要提供接口所必需的方法。

go语言通过隐性的方式实现了接口功能,相对比较灵活。

Go语言接口的特点

  • interface 是方法或行为声明的集合
  • interface接口方式实现比较隐性,任何类型的对象实现interface所包含的全部方法,则表明该类型实现了该接口。
  • interface还可以作为一种通用的类型,其他类型变量可以给interface声明的变量赋值。
  • interface 可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。

二、接口类型

2.1 接口的定义

每个接口类型由任意个方法签名组成,接口的定义格式如下:

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

说明

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

举个例子,定义一个包含Write方法的Writer接口。

type writer interface{
    Write([]byte) error
}

2.2 实现接口的条件

接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口。

示例

定义的Eater接口类型,它包含一个Eat方法。

// Eater 接口
type Eater interface {
	Eat()
}

有一个Dog结构体类型如下。

type Dog struct {}

因为Eater接口只包含一个Eat方法,所以只需要给Dog结构体添加一个Eat方法就可以满足Eater接口的要求。

//Dog类型的Eat方法
func (d Dog) Eat() {
	fmt.Println("吃骨头!")
}

这样就称为Dog实现了Eater接口。

完整代码

// Eater 接口
type Eater interface {
	Eat()
}

type Dog struct {}

//Dog类型的Eat方法
func (d Dog) Eat() {
	fmt.Println("吃骨头!")
}

func main() {
	dog := Dog{}
	dog.Eat()
}

2.3 为什么需要接口

多数情况下,数据可能包含不同的类型,却会有一个或者多个共同点,这些共同点就是抽象的基础。

示例

// Eater 接口
type Eater interface {
	Eat()
}

type Dog struct {}

//Dog类型的Eat方法
func (d Dog) Eat() {
	fmt.Println("狗狗喜欢吃骨头!")
}

type Cat struct {}

func (c Cat) Eat(){
	fmt.Println("小猫喜欢吃鱼!")
}

func main() {
	dog := Dog{}
	dog.Eat()
	cat := Cat{}
	cat.Eat()
}

从动物身上,可以抽象出来一个eat方法,这样即使在扩展其它动物进来,也只需要实现Eater 接口中的Eat()方法就可以完成对这个动作的调用。

接口可以理解为某一个方面的抽象,可以是多对一的(多个类型实现一个接口),这也是多态的体现。

2.4 接口类型变量

一个接口类型的变量能够存储所有实现了该接口的类型变量。

例如在上面的示例中,DogCat类型均实现了Eater接口,此时一个Eater类型的变量就能够接收CatDog类型的变量。

var x Eater // 声明一个Eater类型的变量x
a := Cat{}  // 声明一个Cat类型变量a
b := Dog{}  // 声明一个Dog类型变量b
x = a       // 可以把Cat类型变量直接赋值给x
x.Eat()     // 小猫喜欢吃鱼!
x = b       // 可以把Dog类型变量直接赋值给x
x.Eat()     // 狗狗喜欢吃骨头!

三、值接收者和指针接收者

通过下方一个示例来演示实现接口使用值接收者和使用指针接收者有什么区别。

定义一个Mover接口,它包含一个Move方法。

// Mover 定义一个接口类型
type Mover interface {
	Move()
}

3.1 值接收者实现接口

我们定义一个Dog结构体类型,并使用值接收者为其定义一个Move方法。

// Dog 狗结构体类型
type Dog struct{}

// Move 使用值接收者定义Move方法实现Mover接口
func (d Dog) Move() {
	fmt.Println("狗会动")
}

此时实现Mover接口的是Dog类型。

var x Mover    // 声明一个Mover类型的变量x

var d1 = Dog{} // d1是Dog类型
x = d1         // 可以将d1赋值给变量x
x.Move()

var d2 = &Dog{} // d2是Dog指针类型
x = d2          // 也可以将d2赋值给变量x
x.Move()

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是结构体类型还是对应的结构体指针类型的变量都可以赋值给该接口变量。

3.2 指针接收者实现接口

我们再来测试一下使用指针接收者实现接口有什么区别。

// Cat 猫结构体类型
type Cat struct{}

// Move 使用指针接收者定义Move方法实现Mover接口
func (c *Cat) Move() {
	fmt.Println("猫会动")
}

此时实现Mover接口的是*Cat类型,我们可以将*Cat类型的变量直接赋值给Mover接口类型的变量x

var c1 = &Cat{} // c1是*Cat类型
x = c1          // 可以将c1当成Mover类型
x.Move()

但是不能给将Cat类型的变量赋值给Mover接口类型的变量x

// 下面的代码无法通过编译
var c2 = Cat{} // c2是Cat类型
x = c2         // 不能将c2当成Mover类型

由于Go语言中有对指针求值的语法糖,对于值接收者实现的接口,无论使用值类型还是指针类型都没有问题。但是我们并不总是能对一个值求址,所以对于指针接收者实现的接口要额外注意。

四、类型与接口的关系

4.1 一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

示例

动物不仅有吃的属性,还有动的属性,可以通过定义两个接口,让同一个动物分别实现这两种属性

// Eater 接口
type Eater interface {
	Eat()
}

// Mover 接口
type Mover interface {
	Move()
}

type Dog struct {}

//Dog类型的Eat方法
func (d Dog) Eat() {
	fmt.Println("狗狗喜欢吃骨头!")
}

//Dog类型的Move方法
func (d Dog) Move(){
	fmt.Println("狗狗喜欢玩耍!")
}

func main() {
	//初始化结构体
	dog := Dog{}

	//dog实现了Eater和Mover两个接口
	eat := dog
	move := dog

	eat.Eat()	//对Eater类型调用Eat方法
	move.Move()	//对Mover类型调用Move方法
}

程序中的结构体Dog分别实现了Eater和Mover两个接口中的方法。

4.2 多种类型实现同一接口

Go语言中不同的类型还可以实现同一接口。

一个接口的所有方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。

// WashingMachine 洗衣机
type WashingMachine interface {
	wash()
	dry()
}

// 甩干器
type dryer struct{}

// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
	fmt.Println("甩一甩")
}

// 洗衣机
type haier struct {
	dryer //嵌入甩干器
}

// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
	fmt.Println("洗刷刷")
}

func main() {
	h := haier{}
	h.dry()
	h.wash()
}

五、接口嵌套

接口与接口之间可以通过互相嵌套形成新的接口类型。例如Go标准库io源码中就有很多接口之间互相组合的示例。

// src/io/io.go

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

type Writer interface {
	Write(p []byte) (n int, err error)
}

type Closer interface {
	Close() error
}

// ReadWriter 是组合Reader接口和Writer接口形成的新接口类型
type ReadWriter interface {
	Reader
	Writer
}

// ReadCloser 是组合Reader接口和Closer接口形成的新接口类型
type ReadCloser interface {
	Reader
	Closer
}

// WriteCloser 是组合Writer接口和Closer接口形成的新接口类型
type WriteCloser interface {
	Writer
	Closer
}

对于这种由多个接口类型组合形成的新接口类型,同样只需要实现新接口类型中规定的所有方法就算实现了该接口类型。

接口也可以作为结构体的一个字段,我们来看一段Go标准库sort源码中的示例。

// src/sort/sort.go

// Interface 定义通过索引对元素排序的接口类型
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

// reverse 结构体中嵌入了Interface接口
type reverse struct {
    Interface
}

通过在结构体中嵌入一个接口类型,从而让该结构体类型实现了该接口类型,并且还可以改写该接口的方法。

// Less 为reverse类型添加Less方法,重写原Interface接口类型的Less方法
func (r reverse) Less(i, j int) bool {
	return r.Interface.Less(j, i)
}

Interface类型原本的Less方法签名为Less(i, j int) bool,此处重写为r.Interface.Less(j, i),即通过将索引参数交换位置实现反转。

在这个示例中还有一个需要注意的地方是reverse结构体本身是不可导出的(结构体类型名称首字母小写),sort.go中通过定义一个可导出的Reverse函数来让使用者创建reverse结构体实例。

func Reverse(data Interface) Interface {
	return &reverse{data}
}

这样做的目的是保证得到的reverse结构体中的Interface属性一定不为nil,否者r.Interface.Less(j, i)就会出现空指针panic。

六、空接口

Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。

空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

示例

func main() {
	//定义一个空接口x,x变量可以接收任意的数据类型
	var x interface{}
	str := "Hello Go"
	x = str
	fmt.Printf("type:%T,value:%v\n",x,x)

	num := 10
	x = num
	fmt.Printf("type:%T,value:%v\n",x,x)

	bool := true
	x = bool
	fmt.Printf("type:%T,value:%v\n",x,x)
}

运行结果

type:string,value:Hello Go
type:int,value:10
type:bool,value:true

1、空接口作为函数的参数

// 空接口作为函数参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}

func main() {
	show(1)
	show(true)
	show(3.14)
	var mapStr = make(map[string]string)
	mapStr["name"] = "Leefs"
	mapStr["age"] = "12"
	show(mapStr)
}

运行结果

type:int value:1
type:bool value:true
type:float64 value:3.14
type:map[string]string value:map[age:12 name:Leefs]

2、map的值实现空接口

func main() {
	// 空接口作为 map 值
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "Jeyoo"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)
}

运行结果

map[age:18 married:false name:Jeyoo]

3、切片实现空接口

func main() {
	var slice = []interface{}{"Jeyoo", 20, true, 32.2}
	fmt.Println(slice)
}

运行结果

[Jeyoo 20 true 32.2]

七、类型断言

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

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

x.(T)

说明

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

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

示例

func main() {
	var x interface{}
	x = "Hello GO"
	v, ok := x.(string)
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("类型断言失败")
	}
}

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

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

// justifyType 对传入的空接口类型变量x进行类型断言
func justifyType(x interface{}) {
	switch v := x.(type) {
	case string:
		fmt.Printf("x is a string,value is %v\n", v)
	case int:
		fmt.Printf("x is a int is %v\n", v)
	case bool:
		fmt.Printf("x is a bool is %v\n", v)
	default:
		fmt.Println("unsupport type!")
	}
}

由于接口类型变量能够动态存储不同类型值的特点,所以很多初学者会滥用接口类型(特别是空接口)来实现编码过程中的便捷。

只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。切记不要为了使用接口类型而增加不必要的抽象,导致不必要的运行时损耗。

总结

在 Go 语言中接口是一个非常重要的概念和特性,使用接口类型能够实现代码的抽象和解耦,也可以隐藏某个功能的内部实现,但是缺点就是在查看源码的时候,不太方便查找到具体实现接口的类型。

相信很多读者在刚接触到接口类型时都会有很多疑惑,请牢记接口是一种类型,一种抽象的类型。区别于我们在之前章节提到的那些具体类型(整型、数组、结构体类型等),它是一个只要求实现特定方法的抽象类型。

以上就是Golang接口使用教程详解的详细内容,更多关于Golang接口的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go语言接口的用法详解

    一.接口的定义和好处 我们都知道接口给类提供了一种多态的机制,什么是多态,多态就是系统根据类型的具体实现完成不同的行为. 以下代码简单说明了接口的作用 package main import ( "fmt" "io" "net/http" "os" ) // init 在main 函数之前调用 func init() { if len(os.Args) != 2 { fmt.Println("Usage: ./exa

  • Go基础教程系列之Go接口使用详解

    接口用法简介 接口(interface)是一种类型,用来定义行为(方法). type Namer interface { my_method1() my_method2(para) my_method3(para) return_type ... } 但这些行为不会在接口上直接实现,而是需要用户自定义的方法来实现.所以,在上面的Namer接口类型中的方法my_methodN都是没有实际方法体的,仅仅只是在接口Namer中存放这些方法的签名(签名 = 函数名+参数(类型)+返回值(类型)). 当用

  • 从零开始学Golang的接口

    目录 前言 1.为什么需要接口? 2.接口是什么?如何定义? 3.接口实战初体验 4.如何测试是否已实现该接口? 5.空接口&类型断言 6.接口零值 7.一个类型实现多个接口 8.指针与值类型实现接口的区别 9.接口嵌套 前言 接口在面向对象编程中是经常使用的招式,也是体现多态很重要的手段.是的.Golang中也有接口这玩意儿. 1.为什么需要接口? 多数情况下,数据可能包含不同的类型,却会有一个或者多个共同点,这些共同点就是抽象的基础.前文讲到的Golang继承解决的是is-a的问题,单一继承

  • Go语言接口定义与用法示例

    本文实例讲述了Go语言接口定义与用法.分享给大家供大家参考,具体如下: 在Go中,接口interface其实和其他语言的接口意思也没什么区别.interface理解其为一种类型的规范或者约定.一种类型是不是"实现"了一个接口呢?就看这种类型是不是实现了接口中定义的所有方法. 1. 接口的定义和使用. 比如 复制代码 代码如下: type I interface{     Get() int     Put(int) } 这段话就定义了一个接口,它包含两个函数Get和Put 好了,我的一

  • go语言接口用法实例分析

    本文实例讲述了go语言接口用法.分享给大家供大家参考.具体分析如下: 首先定义一个接口: 复制代码 代码如下: type I interface{     Get() int     Put(int)   } 这段话就定义了一个接口,它包含两个函数Get和Put 好了,我的一个接口实现了这个接口: 复制代码 代码如下: type S stuct {val int} func (this *S) Get int {     return this.val } func (this *S)Put(v

  • Go语言的接口详解

    目录 1.接口的用途 2.类型断言 3.类型选择 4.空接口 5.匿名空接口 6.实现多个接口 7.接口嵌套 8.接口零值 9.make和new的区别 总结 接口就是一系列方法的集合(规范行为) 在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为,规范子类对象的行为. 在 Go 语言中的接口是非侵入式接口(接口没了,不影响代码),侵入式接口(接口没了,子类报错) Go 也是鸭子类型,比如我现在有个鸭子类,内有 speak 方法和 run 方法,子类只要实现了 speak 和 run,我

  • Golang接口使用教程详解

    目录 前言 一.概述 二.接口类型 2.1 接口的定义 2.2 实现接口的条件 2.3 为什么需要接口 2.4 接口类型变量 三.值接收者和指针接收者 3.1 值接收者实现接口 3.2 指针接收者实现接口 四.类型与接口的关系 4.1 一个类型实现多个接口 4.2 多种类型实现同一接口 五.接口嵌套 六.空接口 七.类型断言 总结 前言 go语言并没有面向对象的相关概念,go语言提到的接口和java.c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承.子类.implements关键

  • 使用client-go工具调用kubernetes API接口的教程详解(v1.17版本)

    目录 说明 效果 实现 1.拉取工具源码 2.创建目录结构 查询代码实例 创建deployment资源 更新deployment类型服务 删除deployment类型服务 说明 可以调取k8s API 接口的工具有很多,这里我就介绍下client-go gitlab上client-go项目地址: https://github.com/kubernetes/client-go 这个工具是由kubernetes官方指定维护的,大家可以放心使用 效果 运行完成后,可以直接获取k8s集群信息等 实现 1

  • Golang拾遗之指针和接口的使用详解

    目录 指针和接口 golang的指针 指向interface的指针 总结 指针和接口 golang的类型系统其实很有意思,有意思的地方就在于类型系统表面上看起来众生平等,然而实际上却要分成普通类型(types)和接口(interfaces)来看待.普通类型也包含了所谓的引用类型,例如slice和map,虽然他们和interface同为引用类型,但是行为更趋近于普通的内置类型和自定义类型,因此只有特立独行的interface会被单独归类. 那我们是依据什么把golang的类型分成两类的呢?其实很简

  • Python接口自动化系列之unittest结合ddt的使用教程详解

    前面一篇我们已经把unittest的常用用法都已经讲过了,可能很多小伙伴有个疑问,unittest框架怎么做数据驱动呢?这节我们就来学习一下. 1.unittest 没有自带数据驱动功能. 所以如果使用unittest,同时又想使用数据驱动,那么就可以使用DDT来完成. DDT是 "Data-Driven Tests"的缩写. 资料:http://ddt.readthedocs.io/en/latest/ 2.什么是ddt? 测试步骤相同,代码一样,测试数据不同,当我们输入一组测试数据

  • Go错误和异常CGO fallthrough处理教程详解

    目录 fallthrough 代码示例 执行结果 CGO 错误&异常 处理错误:error 处理异常:panic&recover panic&defer defer执行顺序 总结 对比Java.C++ 错误异常互相转换 fallthrough 在一个 switch 块内,每个 case 无需声明 break 来终止,如果想顺序执行使用fallthrough: 如果我们想强制执行满足条件case的后一个case,也可以通过设置fallthrough的方式: 代码示例 package

  • ABP(现代ASP.NET样板开发框架)系列之二、ABP入门教程详解

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 "DRY--避免重复

  • golang之log rotate详解

    操作系统: CentOS 6.9_x64 go语言版本: 1.8.3 问题描述 golang的log模块提供的有写日志功能,示例代码如下: /* golang log example */ package main import ( "log" "os" ) func main() { logFile,err := os.Create("test1.log") defer logFile.Close() if err != nil { log.F

  • vue从使用到源码实现教程详解

    搭建环境 项目github地址 项目中涉及了json-server模拟get请求,用了vue-router: 关于Vue生命周期以及vue-router钩子函数详解 生命周期 1.0版本 1.哪些生命周期接口 init Created beforeCompile Compiled Ready Attatched Detached beforeDestory destoryed 2.执行顺序 1. 不具有keep-alive 进入: init->create->beforeCompile->

  • Mybatis 创建方法、全局配置教程详解

    总体介绍:MyBatis实际上是Ibatis3.0版本以后的持久化层框架[也就是和数据库打交道的框架]! 和数据库打交道的技术有: 原生的JDBC技术--->Spring的JdbcTemplate技术 这些工具都是提供简单的SQL语句的执行,但是和我们这里学的MyBatis框架还有些不同, 框架是一整套的东西,例如事务控制,查询缓存,字段映射等等. 我们用原生JDBC操作数据库的时候都会经过: 编写sql---->预编译---->设置参数----->执行sql------->

  • Spring整合MyBatis(Maven+MySQL)图文教程详解

    一. 使用Maven创建一个Web项目 为了完成Spring4.x与MyBatis3.X的整合更加顺利,先回顾在Maven环境下创建Web项目并使用MyBatis3.X,第一.二点内容多数是回顾过去的内容 . 1.2.点击"File"->"New"->"Other"->输入"Maven",新建一个"Maven Project",如下图所示: 1.2.请勾选"Create a si

随机推荐