Go中的interface学习
学过Java的同学都知道在Java中接口更像是一种规范,用接口定义了一组方法,下面实现这个接口的类只管按照写好的方法名和返回值去实现就好,内部如何实现是各个方法自己的事情,接口本身不关注。
另外Java中实现接口的类必须显式的声明实现了哪个接口: implement InterfaceName,仔细思考一下会有如下问题:
- 如果你修改了接口名,那么类也得跟着修改;
- 你必须先定义接口,才能去实现它;
- 一个类可以实现多个接口,Java中接口的设计更像是弥补继承的不足,如果你希望实现类再实现一个接口,那么的继续修改实现类。
带着以上几个问题,我们先看Go中如何去使用Interface,再去探讨理论的问题。
1.定义interface
在Go中接口是用type关键字定义,所以可以说interface是一种具有一组方法的类型,这些方法定义了interface的行为。当然,更突出的是Go允许不带任何方法的interface,这种类型的interface叫做empty interface。
下面的例子展示一个结构体实现了两个接口,但是一眼看上去你无法得知他实现了哪两个接口。
type Person interface {
GetAge() int
GetName() string
}
type Car interface {
GetAge() int
GetName() string
}
type Student struct {
age int
name string
}
func (s Student) GetAge() int {
return s.age
}
func (s Student) GetName() string {
return s.name
}
上面有两个接口Person,Car。Stuent结构体有两个方法和这两个结构体中的方法一样,这就表示Student实现两这两个接口,当然你可以试一下将其中的一个方法注释掉。如果你用的GoLand 编辑器,你会发现编辑器左边的Student身上带着的上限的绿色箭头消失了,表示当前结构体没有实现任何方法。
综上:接口中有几个方法,实现者必须完全实现这些方法才表示当前实现的结构体或者自定义类型实现了这个接口。这一点跟Java中并无区别。
另外你是否也发现,其实你可以先去实现方法,在定义接口,因为接口跟实现之间完全没有耦合关系,接口是根据适用方的需求来定义的,完全不必要关心是否有其他地方定义过。
2.使用interface
先来看如下一段代码:
package main
import "fmt"
type Get interface {
Get_name() string
}
type Person struct {
age int16
name string
}
type Student struct {
no int16
name string
}
func (p Person) Get_name() string {
return p.name
}
func (s Student) Get_name() string {
return s.name
}
func Get_name(get Get) string {
return get.Get_name()
}
func main() {
s := Student{12345,"xiaoming"}
name := Get_name(s)
fmt.Println(name)
}
上面定义了一个接口Get,分别定义了两个结构体都去实现了这个接口,那么如何去通过接口帮我们隐藏当前调用的哪个具体的实现呢,关键就在Get_name方法,这个方法的参数是接口本身,里面的实现你可以通过接口去调用他的任何方法,这些调用目前为止跟任何实现没有任何关系。
注意到下面我们在main方法中定义了一个Student类型的对象s,然后将s传入Get_name方法中,这就表示当前接口的实现者是Student,那么他走的就是Student相关的实现逻辑。即当前Get接口类型的变量get存储的是一个Student对象。
3. 如何判断 interface 变量存储的是哪种类型
现实开发中可能会有一种需求:因为Get_name是每个实现者自己去实现的方法,那么如果想在Get_name方法外面加点料,即需要判断当前来的实现者是哪种类型然后针对某种类型的实现者加点料,该如何做呢。
func Get_name(get Get) string {
switch get.(type) {
case Person:
fmt.Println("person")
case Student:
fmt.Println("student")
}
return get.Get_name()
}
注意:interface.(type)方法只能在switch中实现,出了switch方法你就不能用.的方式调出来这个方法了。
.(type)表示当前我们要断言的类型。
4. 空的interface
interface{} 是一个空的 interface 类型,根据前文的定义:一个类型如果实现了一个 interface 的所有方法就说该类型实现了这个 interface,空的 interface 没有方法,所以可以认为所有的类型都实现了 interface{}。如果定义一个函数参数是 interface{} 类型,这个函数应该可以接受任何类型作为它的参数。
func doAnything(i interface{}){
}
这是一个很重要的特性。
4.1 使用空interface作为返回参数接收任一类型返回值
我们来看下面这个例子:
type BaseInfo struct{
userId int
userName string
}
type StudentInfo struct{
BaseInfo
Options []string
}
type StaffInfo struct{
StudentInfo
cardId string
}
func checkData(id int) (interface{} , bool) {
data1 ,ok1 := CheckStaffData(id) // 检查员工信息是否正确,返回StaffInfo
data2 ,ok2 := CheckStudentData(id) // 检查学生信息是否正确,返回StudentInfo
if ok1 {
return data1,ok1
}
if ok2 {
return data2,ok2
}
return nil ,false
}
在checkData方法中我们使用了接口作为第一个返回值的接收参数。任何类型都可以。那么下一个问题来了,我们如何去解析用接口接收的这个返回吹呢?还记得上面用 switch interface.(type)判断的例子吧,所以代码可以这样写:
func getData() {
if data,ok := fetchQuestion(22); ok {
switch data.(type) {
case StaffInfo:
fmt.Println("staffInfo")
case StudentInfo:
fmt.Println("student")
case A:
fmt.Println("A")
case B:
fmt.Println("B")
...
...
...
}
}
}
有没有发现这个switch很有可能会成为巨无霸。
在Java中是如何解决这个问题的呢?Java提供了继承和接口的方法,通过定义一个抽象类,或者一个接口,让每一个case的逻辑去实现这个抽象类或者接口,使用工厂模式去加载需要的实现类即可。每次有变动我们只用去更改工厂类,新增新的实现,getData方法可以不用变动。
那么在Go中如何实现呢?
type InfoType interface {
getType() int
getData(s string)
}
type BaseInfo struct{
userId int
userName string
returnType int8
}
type StudentInfo struct{
BaseInfo
Options []string
}
type StaffInfo struct{
StudentInfo
cardId string
}
func (b BaseInfo) getType() int8 {
return b.returnType
}
func (b BaseInfo) getData(s string) {
b.getData(s)
}
func fetchQuestion(id int) (InfoType , bool) {
data1 ,ok1 := CheckStaffData(id) // 检查员工信息是否正确,返回StaffInfo
data2 ,ok2 := CheckStudentData(id) // 检查学生信息是否正确,返回StudentInfo
if ok1 {
return data1,ok1
}
if ok2 {
return data2,ok2
}
return nil ,false
}
func getData() {
var s string
if data,ok := fetchQuestion(22); ok {
switch data.getType() {
case A:
s = A
case B:
s = B
case C:
s = C
}
data.getData(s)
}
}
在上面代码中,把人员类型抽象成了一个接口,定义了两个方法:
- 获取类型;
- 根据类型选择不同的实现;
BaseInfo和StudentInfo可以分别去实现这个接口。
最关键的是getData方法的修改,case中只用对type赋值即可,最后由getData方法根据type去调用不同的实现。简化了操作。
5. []interface{}的使用
上面刚说interface{}作为参数陈可以承接任何类型,那么[]interface{}作为参数是不是也可以承接任何类型的数组呢?先来试验一下。
package main
import (
"fmt"
)
func getAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"a", "b", "b"}
getAll(names)
}
如果你用的是GoLand,你会发现检查就报错,还没到编译期。

interface{}可以表示任一类型,不表示[]interface{}可以表示任意类型的数组,它只是表示这是一个数组,里面你想装啥都可以。
正确的使用[]interface{}的姿势是这样的,你需要new 一个interface类型的数组对象,将想要的元素塞进这个数组就可以。
package main
import (
"fmt"
)
func getAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"a", "b", "b"}
c := 3
v := make([]interface{},4)
for i,value :=range names {
v[i] = value
}
v[3] = c
getAll(v)
fmt.Println(v)
}
Go中的interface学习的更多相关文章
- SpringBoot中JPA的学习
SpringBoot中JPA的学习 准备环境和项目配置 写一下学习JPA的过程,主要是结合之前SpringBoot + Vue的项目和网上的博客学习一下. 首先,需要配置一下maven文件,有这么两个 ...
- iOS中,在类的源文件(.m)中,@interface部分的作用?
此@interface部分为类扩展(extension). 其被设计出来就是为了解决两个问题的 其一,定义类私有方法的地方. 其二,实现public readonly,private readwr ...
- PHP中的Libevent学习
wangbin@2012,1,3 目录 Libevent在php中的应用学习 1. Libevent介绍 2. 为什么要学习libevent 3. Php libeven ...
- OC中的@interface和java中的区别以及 @implementation @protocol
java 在java中的interface是‘接口’的意思,而java的类声明用class,即接口用interface声明,类是用class声明,是两个独立的部分. 只有在类声明要实现某个接口时, ...
- JS中childNodes深入学习
原文:JS中childNodes深入学习 <html xmlns="http://www.w3.org/1999/xhtml"> <head> <ti ...
- CNCC2017中的深度学习与跨媒体智能
CNCC2017中的深度学习与跨媒体智能 转载请注明作者:梦里茶 目录 机器学习与跨媒体智能 传统方法与深度学习 图像分割 小数据集下的深度学习 语音前沿技术 生成模型 基于贝叶斯的视觉信息编解码 珠 ...
- 图解BERT(NLP中的迁移学习)
目录 一.例子:句子分类 二.模型架构 模型的输入 模型的输出 三.与卷积网络并行 四.嵌入表示的新时代 回顾一下词嵌入 ELMo: 语境的重要性 五.ULM-FiT:搞懂NLP中的迁移学习 六.Tr ...
- python中confIgparser模块学习
python中configparser模块学习 ConfigParser模块在python中用来读取配置文件,配置文件的格式跟windows下的ini配置文件相似,可以包含一个或多个节(section ...
- 【荐】详解 golang 中的 interface 和 nil
golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...
随机推荐
- 2. 2.1查找命令——linux基础增强,Linux命令学习
2.1.查找命令 grep命令 grep 命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并 把匹配的行打印出来. 格式: grep [option] pattern [file] 可使用 ...
- InnoDB存储引擎--学习笔记-redo log
目录 1. 引言 2. 重做日志文件和相关概念介绍 + 2.1. 重做日志文件和bin log + 2.2. LSN(log squence number) 3. 重做日志文件基本工作原理 4. 重做 ...
- java8中stream常用方法详解
map: 用作类型转换 如把集合里面的字符串转为大写,或者一个对象的集合取几个字段转为新的对象集合filter: 过滤 符合条件的集合元素保存下来,不符合条件的去掉flatMap:合并集合,比如Lis ...
- [原创]Flask+uwsgi+virtualenv+nginx部署配置
1.创建工程python2.7版本虚目录: #virtualenv -p /usr/bin/python2.7 CDN_resource #cd CDN_resource #source ./bin/ ...
- 学习笔记-jvm运行时数据区
按照线程私有和共享区域来划分 线程私有 程序计数器 指向当前线程正在执行的字节码行号地址,如果是本地方法,值为undefined 虚拟机中唯一不会oom的区域 为什么会有程序计数器 - java天生多 ...
- 个人永久性免费-Excel催化剂功能第42波-任意字符指定长度随机函数
日常做表过程中,难免会有一些构造数据的场景,构造数据最好是用随机的数据,如随机密码,随机英文字母.数字等.在Excel原生的随机函数Rand中,仅能处理数字的随机,且最终生成的结果也是数字类型.今天E ...
- C#3.0新增功能09 LINQ 基础08 支持 LINQ 的 C# 功能
连载目录 [已更新最新开发文章,点击查看详细] 查询表达式 查询表达式使用类似于 SQL 或 XQuery 的声明性语法来查询 IEnumerable 集合. 在编译时,查询语法转换为对 LIN ...
- [leetcode] 679. 24 Game (Hard)
24点游戏,游戏规则就是利用().+.-. *. /,对四个数字任意运算,可以得出24点则为true. 排列组合问题,最多有A42*A32*A22*4*4*4,也就是12*6*2*4*4=9216种组 ...
- Word公式显示为{EMBED Equation.DSMT4}
具体问题表现为: 添加了Mathtype公式后显示为{EMBED Equation.DSMT4}, 超链接显示为大花括号和描述文本, 页码显示为 page... 具体解决方法如下,(以Office2 ...
- 【MySQL】(一)MySQL 体系结构和存储引擎
1.1.定义数据库和实例 数据库:物理操作系统文件或其他形式文件类型的集合.在MySQL数据库中,数据库文件可以是frm.MYD.MYI.ibd结尾的文件. 实例:MySQL数据库由后台线程以及一个共 ...