interface 接口相关【GO 基础】

interface,接口,相关,go,基础 · 浏览次数 : 0

小编点评

**接口定义** 接口定义了一个方法,该方法可以接受多个类型的值。接口中可以定义多个方法,每个方法可以接受一个特定的类型的值。接口中可以使用嵌套接口来定义更 complex 的接口。 **空接口** 空接口是指没有定义任何方法的接口。空接口可以定义一个空接口,也可以定义一个指向任何类型值的空接口。空接口的值可以存储任何类型的值。 **类型断言** 类型断言允许我们从一个接口类型中获取到另一个接口类型的值。类型断言使用 switch 语句可以实现类型断言。 **接口的应用** 接口可以应用于任何类型的变量。我们可以使用接口类型来定义变量的类型。我们也可以使用接口类型来定义接口的类型。接口可以作为函数的参数,也可以作为字典的值。

正文

〇、接口简介

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。也就是说,接口可以将一种或多种特征归纳到一起,其他不同的对象通过实现此接口,来表示可以具有此类特征,使得不同的类或模块之间进行通信和交互,而不需要了解彼此的具体实现细节,从而提高代码的可重用性和可维护性。此外,接口还可以帮助程序员更好地组织和管理代码。

基于接口如此优秀的基础条件,Go 语言当然也不会或缺它的身影,本文将结合示例来详细介绍下。

一、关于 GO 语言中的接口

1.1 接口的定义

Go 语言提倡面向接口编程。

在 Go 语言中接口(interface)是一种类型,一种抽象的类型。

interface 是一组 method 的集合,是 duck-type programming(鸭式编程,在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定) 的一种体现。

接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)(我们并不关心对象是什么类型,或者它到底是不是鸭子,只关心行为)

关于接口的特点有:

  • 接口是一个或多个方法定义的集合。
  • 任何类型的方法集中,只要拥有该接口‘对应的全部方法’定义,就表示它“实现”了该接口,无须在该类型上显式声明实现了哪个接口,这称为 Structural Typing 结构类型。其中所谓‘对应的方法’,是指有相同名称、相同参数列表(不包括参数名)以及相同返回值。当然,该类型还可以有其他方法。
  • 接口只有方法声明,没有实现,没有数据字段。
  • 接口可以匿名嵌入其他接口,或嵌入到结构中。
  • 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
  • 只有当接口存储的类型和对象都为 nil 时,接口才等于 nil。
  • 接口调用不会做 receiver 的自动转换。
  • 接口同样支持匿名字段方法。
  • 接口也可实现类似 OOP 中的多态。
  • 空接口可以作为任何类型数据的容器。
  • 一个类型可实现多个接口。
  • 接口命名习惯以 er 结尾。

接口的定义格式:

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

如上的一个简单的示例,定义好一个接口后,其实并不能从接口体知道其目的,只能看出来实现其拥有一个方法 Write()。

1.2 为什么要使用接口

知道了接口如何定义后,其实还有另一个疑问,那就是为啥要用接口,它能带来哪些便利呢?

如下分别以普通方式和接口方式示例:

package main

import "fmt"

type Sayer interface {
	Say() string
}

type Cat struct{}
type Dog struct{}

func (c Cat) Say() string { return "喵喵喵" }
func (d Dog) Say() string { return "汪汪汪" }

func main() {
	// 普通方式
	c := Cat{}
	fmt.Println("猫:", c.Say())
	d := Dog{}
	fmt.Println("狗:", d.Say())
	// 接口方式
	var ss Sayer // 接口类型变量,能够存储所有实现了该接口的实例
	ss = Cat{}
	fmt.Println("猫:", ss.Say())
	ss = Dog{}
	fmt.Println("狗:", ss.Say())
}

两种方式都可以达到同样的目的,但是很明显接口方式更具可扩展性,当后续又新增了猪牛羊等动物时,无需再新增变量,都可以赋值给接口 Sayer 对象的实例。

接口区别于我们之前所有的具体类型,接口是一种抽象的类型。当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。

1.3 接口实现的条件

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

例如,上一章节中的示例代码中,接口 Sayer 声明时,只包含一个方法,则 Cat、Dog 只要实现了 Say 方法就算实现了接口。

1.4 值接收者和指针接收者实现接口的区别

值接收者示例代码:

package main

import "fmt"

type Mover interface {
	Move()
}
type dog struct{}

func (d dog) Move() {
	fmt.Println("狗会动")
}

func main() {
	var x Mover
	var wangcai = dog{} // 旺财是 dog 类型
	x = wangcai         // x 可以接收 dog 类型
	x.Move()
	var fugui = &dog{} // 富贵是 *dog 类型
	x = fugui          // x 可以接收 *dog 类型
	x.Move()
}

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是 dog 结构体还是结构体指针 *dog 类型的变量都可以赋值给该接口变量。因为 Go 语言中有对指针类型变量求值的语法糖,dog 指针 fugui 内部会自动求值 *fugui。 

但是,当接收者变更为指针后,接口对象就只能接收指针类型了,如果直接接收对象,则会报异常。如下指针接收者的示例代码:

package main

import "fmt"

type Mover interface {
	Move()
}
type dog struct{}

func (d *dog) Move() {
	fmt.Println("狗会动")
}

func main() {
	var x Mover
	var wangcai = dog{} // 旺财是 dog 类型
	x = wangcai         // x 不可以接收 dog 类型
    // cannot use wangcai (variable of type dog) as Mover value in assignment: dog does not implement Mover (method Move has pointer receiver)compilerInvalidIfaceAssign
	x.Move()
	var fugui = &dog{} // 富贵是 *dog 类型
	x = fugui          // x 可以接收 *dog 类型
	x.Move()
}

二、类型与接口的关系

2.1 一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。就可以分别定义 Sayer 接口和 Mover 接口,然后让 Dog 对象分别实现两个接口,代码如下:

package main

import "fmt"

// 定义两个接口
type Sayer interface {
	Say()
}
type Mover interface {
	Move()
}

type Dog struct {
	name string
}

// 接口的实现
func (d Dog) Say() {
	fmt.Printf("%s会叫汪汪汪\n", d.name)
}
func (d Dog) Move() {
	fmt.Printf("%s会动\n", d.name)
}

func main() {
	var x Sayer // 声明两个接口类型
	var y Mover
	var a = Dog{name: "旺财"}
	x = a // 将对象赋值给接口类型
	y = a
	x.Say()
	y.Move()
}

2.2 多个类型实现同一接口

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

下面一段示例代码,狗和汽车都可以移动,它们都可以实现 Mover 接口:

package main

import "fmt"

type Mover interface {
	Move()
}

type Dog struct {
	name string
}
type Car struct {
	brand string
}

// Dog 类型实现 Mover 接口
func (d Dog) Move() {
	fmt.Printf("%s会跑\n", d.name)
}

// car 类型实现 Mover 接口
func (c Car) Move() {
	fmt.Printf("%s速度 70 迈\n", c.brand)
}

func main() {
	var x Mover
	var a = Dog{name: "旺财"}
	var b = Car{brand: "保时捷"}
	x = a
	x.Move()
	x = b
	x.Move()
}

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

如下示例代码,声明一个洗衣机接口,包含‘洗’和‘甩干’两个方法。海尔洗衣机实现了‘洗’方法,同时其中的甩干器又实现了‘甩干’方法。最终结果就是海尔洗衣机实现了洗衣机的全部方法,即实现了洗衣机接口:

package main

import "fmt"

// 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() {
	var washing_machine WashingMachine
	var haier = Haier{}
	washing_machine = haier
	washing_machine.Dry()
	washing_machine.Wash()
}

2.3 多个接口可以嵌套

接口与接口间可以通过嵌套创造出新的接口。

package main

import "fmt"

// Sayer 接口
type Sayer interface {
	Say()
}

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

// 接口嵌套
type Animal interface {
	Sayer
	Mover
}

// 嵌套得到的接口的使用与普通接口一样
// 如下 Cat 实现 Animal 接口
type Cat struct {
	name string
}

func (c Cat) Say() {
	fmt.Println("喵喵喵")
}

func (c Cat) Move() {
	fmt.Println("猫会动")
}

func main() {
	var x Animal
	x = Cat{name: "花花"}
	x.Move()
	x.Say()
}

三、空接口

3.1 空接口的定义

空接口是指没有定义任何方法的接口。因此任何类型都可视为实现了空接口,也就是说空接口类型的变量可以存储任意类型的变量

如下示例代码,将不同类型赋值给空接口实例:

package main

import "fmt"

func main() {
	// 定义一个空接口x
	var x interface{}
	s := "pprof.cn"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}

 

3.2 空接口的应用

可作为函数的参数:

func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
}

空接口作为 map 的值:

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

package main

import "fmt"

func main() {
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "李白"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)
}

3.3 类型断言

空接口可以存储任意类型的值,那么从中取值的时候怎么判断值类型呢?下面来看下。

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

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:x.(T)。其中,x:表示类型为 interface{} 的变量;T:表示断言 x 可能是的类型。该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言成功,为 false 则表示断言失败。

如下一段简单的示例代码:

package main

import "fmt"

func main() {
	var x interface{}
	x = "测试文本" // 当 x = 1 时,返回“类型断言失败”
	v, ok := x.(string)
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("类型断言失败")
	}
}

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

package main

import "fmt"

func main() {
	var x interface{}
	x = 1
	justifyType(x)
}

func justifyType(x interface{}) {
	switch v := x.(type) { // 返回值为 x 的类型
	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!")
	}
}

关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

参考:http://www.topgoer.com/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/%E6%8E%A5%E5%8F%A3.html

与interface 接口相关【GO 基础】相似的内容:

interface 接口相关【GO 基础】

〇、接口简介 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。也就是说,接口可以将一种或多种特征归纳到一起,其他不同的对象通过实现此接口,来表示可以具有此类特征,使得不同的类或模块之间进行通信和交互,而不需要了解彼此的具体实现细节,从而提高代码的可

[转帖]netstat命令详解

http://blog.itpub.net/69955379/viewspace-2901405/http://blog.itpub.net/69955379/viewspace-2901405/ 简介 netstat 用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface S

C#中接口的显式实现与隐式实现及其相关应用案例

C#中接口的显式实现与隐式实现 最近在学习演化一款游戏项目框架时候,框架作者巧妙使用接口中方法的显式实现来变相对接口中方法进行“密封”,增加实现接口的类访问方法的“成本”。 接口的显式实现和隐式实现: 先定义一个接口,接口中有这两个方法。 public interface ICanSingSong

在基于vue-next-admin的Vue3+TypeScript前端项目中,为了使用方便全局挂载对象接口

在基于vue-next-admin 的 Vue3+TypeScript 前端项目中,可以整合自己的 .NET 后端,前端操作一些功能的时候,为了使用方便全局挂载的对象接口,以便能够快速处理一些特殊的操作,如消息提示、辅助函数、正则测试等等。本篇随笔介绍在Vue3+TypeScript 前端项目中全局挂载对象$u,获得相关 $u_interface 的统一入口的接口信息。这样在组件或者页面中就可以方

Linux内核之SPI协议

SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行的行业标准,但是并没有像I2C那样有标准文档,它还有主从、可片选的特性。 图源自Serial Peripheral Interface-wikipedia 时序图 放个经典老图,来源未知。相位和极性决定了采样

[转帖]焱融全闪系列科普| 为什么 SSD 需要 NVMe?

https://xie.infoq.cn/article/7026237b455c7d62f33afc4a9 NVMe 的由来 目前机械硬盘大多数使用 SATA (Serial ATA Advanced Host Controller Interface) 接口,接口协议为 AHCI,是 Intel

[转帖]焱融全闪系列科普| 为什么 SSD 需要 NVMe?

https://aijishu.com/a/1060000000371942 云计算存储极术推荐 NVMe 的由来 目前机械硬盘大多数使用 SATA (Serial ATA Advanced Host Controller Interface) 接口,接口协议为 AHCI,是 Intel 联合多家公

高德面试:为什么Map不能插入null?

在 Java 中,Map 是属于 java.util 包下的一个接口(interface),所以说“为什么 Map 不能插入 null?”这个问题本身问的不严谨。Map 部分类关系图如下: 所以,这里面试官其实想问的是:为什么 ConcurrentHashMap 不能插入 null? 1.HashM

TypeScript数据类型

目录TypeScript数据类型基础数据类型number、string、boolean、 null 和 undefined、object其他数据类型元组 []枚举 enum接口 interface联合类型 |交叉类型 &type声明面向对象类class继承extends其他类型推断类型断言 as总结

深入理解java和dubbo的SPI机制

1 SPI简介 1.1 SPI(Service Provider Interface) 本质:将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。 java SPI:用来设计给服务提供商做插件使用的。基于策略模式来实现动态加载的机制。我