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

接口

接口类型

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

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

为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。

为什么要使用接口

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函数中明显有重复的代码,如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢?

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

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

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

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

Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一种抽象的类型。当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。

接口的定义

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

每个接口由数个方法组成,接口的定义格式如下:

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

}

其中:

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

举个例子:

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

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

实现接口的条件

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

我们来定义一个Sayer接口:

// Sayer 接口
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类型的变量。

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 {}

值接收者实现接口

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

此时实现接口的是dog类型:

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

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

指针接收者实现接口

同样的代码我们再来测试一下使用指针接收者有什么区别:

func (d *dog) move() {
fmt.Println("狗会动")
}
func main() {
var x Mover
var wangcai = dog{} // 旺财是dog类型
x = wangcai // x不可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
}

此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时x只能存储*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{}
think := "bitch"
fmt.Println(peo.Speak(think))
}

类型与接口的关系

一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。我们就分别定义Sayer接口和Mover接口,如下: 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
var y Mover var a = dog{name: "旺财"}
x = a
y = a
x.say()
y.move()
}

多个类型实现同一接口

Go语言中不同的类型还可以实现同一接口 首先我们定义一个Mover接口,它要求必须由一个move方法。

// Mover 接口
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)
}

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

func main() {
var x Mover
var a = dog{name: "旺财"}
var b = car{brand: "保时捷"}
x = a
x.move()
x = b
x.move()
}

上面的代码执行结果如下:

旺财会跑
保时捷速度70迈

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

// 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("洗刷刷")
}

接口嵌套

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

// 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()
}

空接口

空接口的定义

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

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

func main() {
// 定义一个空接口x
var x interface{}
s := "Hello 沙河"
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)
}

空接口的应用

空接口作为函数的参数

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

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

空接口作为map的值

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

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

类型断言

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

接口值

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

我们来看一个具体的例子:

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

请看下图分解:

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

x.(T)

其中:

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

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

举个例子:

func main() {
var x interface{}
x = "Hello 沙河"
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 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语言中的使用十分广泛。

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

接口实例练习

//package main
//
//import "fmt"
//
//type HUhi interface {
// sayHi()
//}
//
//type student1 struct {
//}
//
//func (s *student1) sayHi() {
// fmt.Println("student hi....")
//}
//
//type dog struct {
//}
//
//func (d dog) sayHi() {
// fmt.Println("dog hi....")
//}
//
//func whoHi(uhi HUhi) {
// uhi.sayHi()
//}
//
//type Integer int
//
//func (a Integer) Less(b Integer) bool {
// return a < b
//}
//// Go可以根据"类型"的函数,自动生成"类型指针"的函数.比如根据Less函数自动生成了
//func (a *Integer) LessAuto(b Integer) bool {
// return (*a).Less(b) //注意:这里是【(*a).Less(b)】而不是【*a < b】
//}
////其实就是"类型指针"的函数调用了"类型"的函数.下面的函数LessAuto就是自动生成的Less的具象形式.
//
//func (a *Integer) add(b Integer) bool {
// return (*a) < b
//}
//
//func (a Integer) addAuto(b Integer) bool {
// return a.add(b)
//}
//
//func main() {
// s := student1{}
// d := dog{}
// whoHi(&s)
// whoHi(&d)
//
// defer fmt.Println("test")
// defer fmt.Println("defer test")
//
//} // 模拟动物行为的接口
package main import "fmt" type IAnimal interface {
Eat() // 描述吃的行为
} // 动物 所有动物的父类
type Animal struct {
Name string
} // 动物去实现IAnimal中描述的吃的接口
func (a Animal) Eat() {
fmt.Printf("%v is eating\n", a.Name)
} //// 动物的构造函数
//func newAnimal(name string) *Animal {
// return &Animal{
// Name: name,
// }
//}
//
//// 猫的结构体 组合了animal
//type Cat struct {
// *Animal
//}
//
//// 实现猫的构造函数 初始化animal结构体
//func newCat(name string) *Cat {
// return &Cat{
// Animal: newAnimal(name),
// }
//}
//
//// 猫结构体IAnimal的Eat方法
//func (cat *Cat) Eat() {
// fmt.Printf("children %v is eating\n", cat.Name)
//} func main() {
//cat := newCat("cat")
//cat.Eat() // cat is eating
a := &Animal{"jadeshu"}
(*a).Eat()
var iAnimal IAnimal
iAnimal = a
iAnimal.Eat() } //package main
//
//import (
// "fmt"
//)
//
//// 调用器接口
//type Invoker interface {
// // 需要实现一个Call方法
// Call(interface{})
//}
//
//// 结构体类型
//type Struct struct {
//}
//
//// 实现Invoker的Call
//func (s *Struct) Call(p interface{}) {
// fmt.Println("from struct", p)
//}
//
//// 函数定义为类型
//type FuncCaller func(interface{})
//
//// 实现Invoker的Call
//func (f FuncCaller) Call(p interface{}) {
// // 调用f函数本体
// f(p)
//}
//func main() {
// // 声明接口变量
// var invoker Invoker
// // 实例化结构体
// s := new(Struct)
// // 将实例化的结构体赋值到接口
// invoker = s
// // 使用接口调用实例化结构体的方法Struct.Call
// invoker.Call("hello")
// // 将匿名函数转为FuncCaller类型,再赋值给接口
// invoker = FuncCaller(func(v interface{}) {
// fmt.Println("from function", v)
// })
// // 使用接口调用FuncCaller.Call,内部会调用函数本体
// invoker.Call("hello")
//}

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

  1. C# 语言规范_版本5.0 (第13章 接口)

    1. 接口 一个接口定义一个协定.实现某接口的类或结构必须遵守该接口定义的协定.一个接口可以从多个基接口继承,而一个类或结构可以实现多个接口. 接口可以包含方法.属性.事件和索引器.接口本身不提供它所 ...

  2. <NET CLR via c# 第4版>笔记 第13章 接口

    13.1 类和接口继承 13.2 定义接口 C#用 interface 关键字定义接口.接口中可定义方法,事件,无参属性和有参属性(C#的索引器),但不能定义任何构造器方法,也不能定义任何实例字段. ...

  3. 《CLR.via.C#第三版》第二部分第13章节 接口 读书笔记(七)

    这章的书写感觉很普通,是些基础的认知知识. 其中一点的重要认知,泛型接口的好处(其实也是使用泛型的好处之一):编译时类型安全&处理值类型时减少装箱. 再说点书上没有的.本来这些知识我打算另外分 ...

  4. 13.Python接口自动化测试 -- 豆瓣

    1.代码如下所示: import requests,unittest import json import HTMLTestRunner class TestDouBan(unittest.TestC ...

  5. 13. 抽象类 & 接口

    一.抽象类 // 抽象类Shape public abstract class Shape { // 1. 成员变量 private String color; // 2. 初始化块 { System ...

  6. 13.7Cloneable接口

    要点提示:Cloneable接口给出了一个可克隆对象.

  7. 13.6Comparable接口

    要点提示:Comparable接口定义了conpareTo方法,用于比较对象. public interface Comparable<E>{ piblic int compareTo(E ...

  8. java 接口

    1.接口的引出:发现没有继承关系的类也能共享行为 2.接口不是类,类描述对象的属性和行为,但是接口只关注实现的行为3.当我们发现有行为在多个没有继承关系的类中共享,我们要把它抽取到接口中,而不是写到父 ...

  9. ArcGIS Engine开发之旅04---ARCGIS接口详细说明

    原文:ArcGIS Engine开发之旅04---ARCGIS接口详细说明 ArcGIS接口详细说明... 1 1.      IField接口(esriGeoDatabase)... 2 2.    ...

随机推荐

  1. linux 压力测试工具之ab

    简介 Apache Benchmark(简称ab) 是Apache安装包中自带的压力测试工具 ,简单易用 在此提供 ab 在 centOS7 下的安装和使用方法注:个人发现,之前安装的centos6. ...

  2. ASP.NET SignalR 系列(三)之代码实现

    说在前头: 因SignalR默认采用camel的编码规范,故前端调用后端的对象或者方法时,首字母均需要小写 创建集线器 创建完,文件中默认创建了一个不带参数Hello方法的示例,我们修改一下,带个参数 ...

  3. 用GraphicsMagick处理svg转png遇到的坑

    1前言 用GraphicsMagick处理svg转png,且背景是透明且没有黑边,由于使用虚拟机的gm版本是1.3.28导致有黑边问题且svg中path中有opacity属性时,加上+antialia ...

  4. python抓取贝壳房源信息

    分析了贝壳的房源信息数据,发现地址链接的参数传递是有规律的 https://tj.ke.com/chengjiao/a3l4/ a3 实际表示的 l4 表示的是 然后 将复合条件拼成一个字符串,带过去 ...

  5. Fluxay流光使用

    扫描IPC主机 填写扫描地址.扫描类型为NT/98 显示如下,扫描成功 扫描用户列表 显示如下,扫描成功 下面想怎么做就怎么做 IPC连接失败原因 对方未打开IPC共享 对方未开启139或445端口 ...

  6. python检测远程udp端口是否打开的代码

    研发过程,把开发过程较好的代码收藏起来,如下的代码内容是关于python检测远程udp端口是否打开的代码,希望对各朋友有较大帮助. import socketimport threadingimpor ...

  7. 知识扩展——(转)一篇文章彻底弄懂Base64编码原理

    在互联网中的每一刻,你可能都在享受着Base64带来的便捷,但对于Base64的基础原理又了解多少?今天这篇博文带领大家了解一下Base64的底层实现. 一.Base64的由来 目前Base64已经成 ...

  8. Java字符串操作工具类

    import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.lang. ...

  9. 二十六、聊聊mysql如何实现分布式锁

    分布式锁的功能 分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 锁具有重入的功能:即一个使用者可以多次获取某个锁 获取锁有超时的功能:即在指定的时间内去尝试获取锁,超过了超时 ...

  10. PHP生成小程序二维码

    /** * [生成小程序二维码] * @return [type] [description] */ public function makeMiniQrcode_do() { begin: $id ...