接口

接口(interface)定义了一个对象的行为规范,之定义规范不实现,由具体的对象来实现规范的细节。

接口类型

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

interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我们就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

为什么要使用接口

type Cat struct {}

func (c Cat) Say() string{
return "喵喵喵"
} type Dog struct{} func (d Dog) Say() string{
return "汪汪汪"
} func main() {
c := Cat{}
fmt.Println("猫:", c.Say()) // 猫: 喵喵喵
d := Dog{}
fmt.Println("狗:",d.Say()) // 狗: 汪汪汪
}

上面的代码中定义了猫和狗,然后它们都会叫,main函数中明显有重复的代码,如果我们后续再加上猪、羊等动物的话,代码还会一直重复下去,name能不能把它们当成"能叫的动物"来处理呢?

像类似的例子我们在编码过程中经常会遇到:

比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理?

比如三角形,四边形,圆形都能计算周长和面积,能不能把它们当成“图形”来处理呢?

比如销售、行政、程序员都能计算月薪,能不能把他们当成“员工”来处理呢?

Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别我们之前所有的具体类型,接口是一种抽象的类型。

接口的定义

Go语言提倡面向接口编程。每个接口由数个方法组成。

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

示例:

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

当看到这个接口类型的值时,我们不知道它是什么,唯一知道的是可以通过它的Write方法来做一些事情。

实现接口的条件

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

type Sayer interface{
say()
}

定义dogcat两个结构体

type dog struct{}
type cat struct{}

因为Sayer接口中已经实现了一个say方法,所以我们只需要给dogcat分别实现say方法就实现Sayer这个接口了。

// dog实现了Sayer接口
func (d dog) say{
fmt.Println("汪汪汪")
} // cat实现了Sayer接口
func (c cat) say(){
fmt.Println("喵喵喵")
}

接口类型变量

接口类型变量能够存储所有实现了该接口的示例。Sayer类型的变量能够存储DogCat类型的变量。

type Cat struct {}

func (c Cat) Say(){
fmt.Println("汪汪汪")
} type Dog struct{} func (d Dog) Say(){
fmt.Println("喵喵喵")
}
type Sayer interface {
Say()
} func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := Cat{} // 实例化一个Cat
b := Dog{} // 实例化一个Dog
x = a // 可以把Cat实例直接赋值给x
x.Say() // 汪汪汪
x = b // 可以把Dog实例直接赋值给x
x.Say() // 喵喵喵
}

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

定义一个Mover接口和一个dog结构体

type Mover interface{
move()
} type dog struct{
name string
}

值接收者实现接口

func (d dog) move(){
fmt.Printf("%s在快乐的奔跑\n",d.name)
} func main() {
var x Mover
var wangcai dog = dog{
name: "旺财",
}
x = wangcai // 旺财是dog类型
x.move() // 旺财在快乐的奔跑 fugui := &dog{
name: "富贵",
}
x = fugui // 富贵是*dog类型
x.move() // 富贵在快乐的奔跑
}

从上面的代码可以发现,使用值接收者实现接口后,不管是dog结构体还是结构体指针dog类型的变量都可以复制给该接口变量。

因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部自动求值*fugui

指针接收者实现接口

func(d *dog) move(){
fmt.Printf("%s在快乐的奔跑", d.name)
} func main() {
var x Mover
//wangcai := dog{ // 旺财是dog类型
// name: "旺财",
//}
// x = wangcai // move method has pointer receiver // x不可以接收dog类型
// x.move()
fugui := &dog{ // 富贵是*dog类型
name: "富贵",
}
x = fugui
x.move() // 富贵在快乐的奔跑
}

此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时只能存储*dog类型的值。

面试题

观察下面的这段代码,然后回答这段代码能不能通过编译?

type People interface{
Speak(string) string
} type Student struct{} func (stu *Student) Speak(think string) (talk string){
if think == "sb"{
talk = "你是个大帅比"
} else {
talk = "您好"
}
return
} func main(){
var peo People = Student{} // var peo People = &Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}

类型与接口的关系

一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立。例如狗可以叫,也可以动,那么我们就可以定义Sayer接口和Mover接口。

// Sayer 接口
type Sayer interface {
say()
} // Mover 接口
type Mover interface {
move()
}

dog既可以实现Sayer接口,也可以实现Mover接口。

type dog struct {
name string
} // 实现Sayer接口
func (d dog) say(){
fmt.Printf("%s在汪汪叫\n", d.name)
} // 实现Mover接口
func (d dog) move(){
fmt.Printf("%s在欢乐的奔跑\n", d.name)
} func main() {
var (
x Sayer
y Mover
)
a := dog{
name:"旺财",
}
x = a
y = a
x.say()
y.move()
}

多个类型实现同一接口

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

// Mover 接口
type Mover interface{
move()
} type dog struct{
name string
} type cat struct{
name string
} // dog类型实现Mover接口
func (d dog) move(){
fmt.Printf("%s在欢乐的奔跑\n", d.name)
} // car类型实现Mover接口
func (c car) move(){
fmt.Printf("%s在懒洋洋的散步\n", c.name)
}

这时候我们在代码中就可以把狗和猫当成动物来处理,不需要关注他们具体是什么,只需要调用它们的move方法就可以。

func main(){
var x Mover
var a = dog{name:"旺财"}
var b = cat{name:"Tom"}
x = a
x.move() // 旺财在欢乐的奔跑
x = b
x.move() // Tom在懒洋洋的散步
}

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

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 w WashingMachine
var h haier
w = h
w.dry() // 甩一甩
w.wash() // 洗刷刷
}

接口嵌套

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

// Sayer 接口
type Sayer interface {
say()
} // Mover 接口
type Mover interface {
move()
} // 接口嵌套
type animal interface {
Sayer
Mover
} type cat struct{
name string
} func (c cat) move(){
fmt.Printf("%s在懒洋洋的散步\n", c.name)
} func (c cat) say(){
fmt.Printf("%s在喵喵的叫\n", c.name)
} func main() {
c := cat{
name: "Tom",
}
var a animal
a = c
a.say() // Tom在喵喵的叫
a.move() // Tom在懒洋洋的散步
}

空接口

空接口的定义

空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

func main() {
// 定义一个空接口x
var x interface{}
s := "多想和你一样臭不要脸"
x = s
fmt.Printf("type: %T value:%v\n",x,x) // type: string value:多想和你一样臭不要脸
i := 100
x = i
fmt.Printf("type:%T value:%v\n",x,x) // type:int value:100
b := true
x = b
fmt.Printf("type:%T value:%v\n",x,x) // type:bool value:true
}

空接口的应用

空接口作为函数的参数

使用空接口可以实现函数接收任意类型的参数。

func show(a interface{}){
fmt.Printf("type:%T value:%v\n",a ,a)
} func main() {
show([...]string{"Negan","Egon"}) //type:[2]string value:[Negan Egon]
show([]string{"Negan","Egon"}) //type:[]string value:[Negan Egon]
}
空接口座位map的值

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

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

类型断言

空接口可以存储任意类型的值,那如何获取其存储的具体数据呢?

接口值

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

想要判断空接口的值,可以使用类型断言

x.(T)
// x表示类型为interface{}的变量
// T表示断言,x可能的类型

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

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

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

func justifyType(x interface{}){
switch v:=x.(type) {
case string:
fmt.Printf("x is string,value is %s\n",v)
case int:
fmt.Printf("x is string,value is %s\n",v)
case bool:
fmt.Printf("x is string,value is %s\n",v)
default:
fmt.Printf("x is string,value is %s\n",v)
}
} func main() {
justifyType("hello Negan") //x is string,value is hello Negan
}

因为空接口可以存储任意类型值的特点,所以空接口在Go中使用十分广泛,

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

Go中的interface(接口)的更多相关文章

  1. java中的interface(接口)

    概念 usb插槽就是现实中的一个接口 你可以把u盘都插在usb插槽上,而不用担心买来的u盘插不进插槽中不管是插电脑,还是插相机,还是插收音机原因是做usb的厂家和做各种设备的厂家都遵守了统一的规定包括 ...

  2. java中的interface接口

    接口:java接口是一些方法表征的集合,但是却不会在接口里实现具体的方法. java接口的特点如下: 1.java接口不能被实例化 2.java接口中声明的成员自动被设置为public,所以不存在pr ...

  3. 发现TypeScript中同名interface接口会自动合并的特性

    今天在学习怎么用TypeScript给jQuery写扩展插件时发现一个很有趣的事情

  4. go中的数据结构接口-interface

    1. 接口的基本使用 golang中的interface本身也是一种类型,它代表的是一个方法的集合.任何类型只要实现了接口中声明的所有方法,那么该类就实现了该接口.与其他语言不同,golang并不需要 ...

  5. java中什么是Interface接口, 请给个实例!

    1.Interface接口的定义和用法  先直接上大白话:马克-to-win:接口就是灰常灰常抽象的抽象类,我们可以就像用抽象类一样用接口,只不过,interface抽象到不能再抽象了,以至于里面不能 ...

  6. Java中的内部接口

    什么是内部接口 内部接口也称为嵌套接口,即在一个接口内部定义另一个接口.举个例子,Entry接口定义在Map接口里面,如下代码: public interface Map { interface En ...

  7. TypeScript Interface(接口)

    类型检查专注于解析值所具有的"形态",这是TypeScript的核心原则之一.这个有时候被称为"duck typing"或者"structural s ...

  8. C#中抽象类和接口

    下面是我做的测试: using System; using System.Collections.Generic; using System.Linq; using System.Text; usin ...

  9. java 子接口中定义与父接口相同的方法

    今天碰到一个很有意思的问题,在java中如果子接口中定义了与父接口中已经有的方法会发生什么事情呢?比如: interface IRunnable extends Runnable{ void run( ...

  10. as3.0 interface接口使用方法

    [转]as3.0 interface接口使用方法 AS在2.0的时候就支持接口了 接口能够让你的程序更具扩展性和灵活性,打个例如 比方你定义了一个方法 代码: public function aMet ...

随机推荐

  1. Windows 提权-内核利用_2

    本文通过 Google 翻译 Kernel Exploits Part 2 – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校 ...

  2. 告别手动敲代码!VSCODE 风格在线可视化开发平台,效率飙升!

    2025 年 2 月 21 日消息,对于广大开发者而言,今天迎来一则重大利好消息!一款操作布局与 VSCODE 极为相似的可视化开发神器 --Joker 智能可视化开发平台正式上线.无论你是深耕前端领 ...

  3. Hololens2 开发(仿真器)配置

    博客地址:https://www.cnblogs.com/zylyehuo/ 参考链接 1.hololens 开发(仿真器)环境配置 2.visual studio 2019安装后添加工作负载 3.H ...

  4. SpringBoot把本地的对象封装成为Nacos的配置对象

    你需要有个Nacos Nacos建立你的配置文件--建议yml文件 编写你的yml配置 platform: transaction: properties: notifyHost: "htt ...

  5. 如何避免VMware平台ESXi主机CPU使用率的“坑”?

    https://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&mid=2650636818&idx=1&sn=c43f3a3146092f ...

  6. 导入SpaceClaim的iges模型尺寸被放大1000倍的问题

    问题 ANSYS APDL 和 Workbench 联合仿真时,导入 SpaceClaim 的 .iges 模型尺寸被放大 1000 倍数. 如 APDL 生成的尺寸为 10 mm(注:此处的 mm ...

  7. Url base64加密

    class UrlEncryption { /** * base64编码 * * @param string * @return string */ public static function en ...

  8. Windows桌面应用自动更新解决方案SharpUpdater5发布

    SharpUpdater是什么 SharpUpdater是一套C#桌面应用自动更新解决方案.基本上,所有自动更新程序的原理都一样:生成一份文件清单,将本地清单与云上清单对比后进行全量更新或增量更新.本 ...

  9. langchain0.3教程:从0到1打造一个智能聊天机器人

    在上一篇文章<大模型开发之langchain0.3(一):入门篇> 中已经介绍了langchain开发框架的搭建,最后使用langchain实现了HelloWorld的代码案例,本篇文章将 ...

  10. 【SpringCloud】Hystrix熔断器

    Hystrix熔断器 概述 分布式系统面临的问题 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败 服务雪崩 多个微服务之间调用的时候,假 ...