interface 接口相关【GO 基础】
〇、接口简介
接口(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 基础】的更多相关文章
- Golang基础(8):go interface接口
一:接口概要 接口是一种重要的类型,他是一组确定的方法集合. 一个接口变量可以存储任何实现了接口方法的具体值.一个重要的例子就是io.Reader和io.Writer type Reader inte ...
- java接口相关例题
java接口相关习题 interface Graphics{ //接口里面只能用抽象方法 public abstract double area(); }//设置 平面类class Plan ...
- Golang 入门系列(四)如何理解interface接口
前面讲了很多Go 语言的基础知识,包括go环境的安装,go语言的语法等,感兴趣的朋友,可以先看看之前的文章.https://www.cnblogs.com/zhangweizhong/category ...
- JAVA 构造器, extends[继承], implements[实现], Interface[接口], reflect[反射], clone[克隆], final, static, abstrac
记录一下: 构造器[构造函数]: 在java中如果用户编写类的时候没有提供构造函数,那么编译器会自动提供一个默认构造函数.它会把所有的实例字段设置为默认值:所有的数字变量初始化为0;所有的布尔变量设置 ...
- as3.0 interface接口使用方法
[转]as3.0 interface接口使用方法 AS在2.0的时候就支持接口了 接口能够让你的程序更具扩展性和灵活性,打个例如 比方你定义了一个方法 代码: public function aMet ...
- 微信公众号平台接口开发:基础支持,获取access_token
新建Asp.net MVC 4.0项目 WeChatSubscript是项目UI层 WeChatTools是封装操作访问公众号接口的一些方法类库 获取AccssToken 我们要的得到AccessTo ...
- 微信公众号平台接口开发:基础支持,获取微信服务器IP地址
官方说明 目前看不出来这个接口有哪些具体运用,但是既然有这个接口,那我们就试试能不能用 访问接口 修改WeCharBase.cs,新增以下2个方法 public static string Serve ...
- interface接口
当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示,就是接口 interface. 定义接口使用的关键字不是class,是interface.接口中常见的成员: 这些成员都有 ...
- golang面向对象和interface接口
一. golang面向对象介绍 1.golang也支持面向对象编程,但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言.2.golang没有类(class),golang语言的结合体(struc ...
- go interface接口
一:接口概要 接口是一种重要的类型,他是一组确定的方法集合. 一个接口变量可以存储任何实现了接口方法的具体值.一个重要的例子就是io.Reader和io.Writer type Reader inte ...
随机推荐
- IDEA的安装准备
IDEA的安装 第一步 第二步 第三部 第四步
- RPA自动化如何帮助企业提高业务业务洞察力
目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ...
- 提升性能的利器:深入解析SectionReader
一. 简介 本文将介绍 Go 语言中的 SectionReader,包括 SectionReader的基本使用方法.实现原理.使用注意事项.从而能够在合适的场景下,更好得使用SectionReader ...
- 国标GB28181协议客户端开发(四)实时视频数据传输
国标GB28181协议客户端开发(四)实时视频数据传输 本文是<国标GB28181协议设备端开发>系列的第四篇,介绍了实时视频数据传输的过程.通过解读INVITE报文中的SDP信息,读取和 ...
- Ubuntu DC + Samba4 AD 实现双域控主机模
文章将讲解如何使用 Ubuntu 16.04 服务器版系统来创建第二台 Samba4 域控制器,并将其加入到已创建好的 Samba AD DC 林环境中,以便为一些关键的 AD DC 服务提供负载均衡 ...
- Java实现数组去重复的18种写法
说明 数组(含List)去重复在日常工作中经常遇到,很多时候用到Set数据结构,但有时候我们需要针对数据进行干预,这时候就需要用其他的实现方式了.以下列出各种的去重方式,基本含括了所有情况. 源码下载 ...
- 2021-10-09 Core学习
控制器学习 如果有ID参数,根据前面定义的{controller=Home}/{action=Index}/{id?} 可以换成一下格式 页面学习 视图 基架搭建 然后在nuget控制台添加 Add- ...
- python连接数据库及查询包含中文错误解决方法
使用MySQLdb库来连接数据库 import MySQLdb conn = MySQLdb.connect(host='127.0.0.1', user='root', passwd='', por ...
- ENVI、ERDAS计算Landsat 7地表温度:单窗算法实现
本文介绍基于ENVI与ERDAS软件,对Landsat 7遥感影像数据加以单窗算法的地表温度(LST)反演操作. 目录 1 原理部分与前期操作准备 1.1 图像预处理 1.2 植被指数反演 1.3 单 ...
- ABC274 题解
A 题目:给定 \(A,B\) 输出 \({B}\over{A}\) 保留 \(3\) 位小数. 简答题,和A+B problem 一样,除一除,保留一下小数. B 题目:给定一个 \(n\) 行 \ ...