学过Java的同学都知道在Java中接口更像是一种规范,用接口定义了一组方法,下面实现这个接口的类只管按照写好的方法名和返回值去实现就好,内部如何实现是各个方法自己的事情,接口本身不关注。

另外Java中实现接口的类必须显式的声明实现了哪个接口: implement InterfaceName,仔细思考一下会有如下问题:

  1. 如果你修改了接口名,那么类也得跟着修改;
  2. 你必须先定义接口,才能去实现它;
  3. 一个类可以实现多个接口,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学习的更多相关文章

  1. SpringBoot中JPA的学习

    SpringBoot中JPA的学习 准备环境和项目配置 写一下学习JPA的过程,主要是结合之前SpringBoot + Vue的项目和网上的博客学习一下. 首先,需要配置一下maven文件,有这么两个 ...

  2. iOS中,在类的源文件(.m)中,@interface部分的作用?

      此@interface部分为类扩展(extension). 其被设计出来就是为了解决两个问题的 其一,定义类私有方法的地方. 其二,实现public readonly,private readwr ...

  3. PHP中的Libevent学习

    wangbin@2012,1,3 目录 Libevent在php中的应用学习 1.      Libevent介绍 2.      为什么要学习libevent 3.      Php libeven ...

  4. OC中的@interface和java中的区别以及 @implementation @protocol

      java 在java中的interface是‘接口’的意思,而java的类声明用class,即接口用interface声明,类是用class声明,是两个独立的部分. 只有在类声明要实现某个接口时, ...

  5. JS中childNodes深入学习

    原文:JS中childNodes深入学习 <html xmlns="http://www.w3.org/1999/xhtml"> <head> <ti ...

  6. CNCC2017中的深度学习与跨媒体智能

    CNCC2017中的深度学习与跨媒体智能 转载请注明作者:梦里茶 目录 机器学习与跨媒体智能 传统方法与深度学习 图像分割 小数据集下的深度学习 语音前沿技术 生成模型 基于贝叶斯的视觉信息编解码 珠 ...

  7. 图解BERT(NLP中的迁移学习)

    目录 一.例子:句子分类 二.模型架构 模型的输入 模型的输出 三.与卷积网络并行 四.嵌入表示的新时代 回顾一下词嵌入 ELMo: 语境的重要性 五.ULM-FiT:搞懂NLP中的迁移学习 六.Tr ...

  8. python中confIgparser模块学习

    python中configparser模块学习 ConfigParser模块在python中用来读取配置文件,配置文件的格式跟windows下的ini配置文件相似,可以包含一个或多个节(section ...

  9. 【荐】详解 golang 中的 interface 和 nil

    golang 的 nil 在概念上和其它语言的 null.None.nil.NULL一样,都指代零值或空值.nil 是预先说明的标识符,也即通常意义上的关键字.在 golang 中,nil 只能赋值给 ...

随机推荐

  1. CentOS 网络互通情况下把一个Linux服务器的文件发送到另一个服务器

    scp -r  文件名/目录名 root@192.168.2.144:/home/hsw -r   发送目录使用,表示把该目录下的所有子目录以及文件发送过去

  2. C# “从元数据”

    所谓元数据.其实是指由微软所封装的类..这些类你可以查看.但是无法修改. 元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述.将您的代码编译 ...

  3. Android使用WebView加载H5页面播放视频音频,退出后还在播放问题解决

    Android中经常会使用到WebView来加载H5的页面,如果H5页面中有音频或者视频的播放时,还没播放完就退出界面,这个时候会发现音频或者视频还在后台播放,这就有点一脸懵逼了,下面是解决方案: 方 ...

  4. 剑指offer第二版-3.数组中重复的数

    面试题3:数组中重复的数 题目要求: 在一个长度为n的数组中,所有数字的取值范围都在[0,n-1],但不知道有几个数字重复或重复几次,找出其中任意一个重复的数字. 解法比较: /** * Copyri ...

  5. 理解vue-loader

    事情的起源是被人问到,一个以.vue结尾的文件,是如何被编译然后运行在浏览器中的?突然发现,对这一块模糊的很,而且看mpvue的文档,甚至小程序之类的都是实现了自己的loader,所以十分必要抽时间去 ...

  6. Android开发-实现第三方APP跳转

    自己创建一个按钮: <Button android:id="@+id/btn_button" android:layout_width="fill_parent&q ...

  7. 20131222-Dom省市加载-第二十七天

    [1]省市选择 <head> <title></title> <script type="text/javascript"> win ...

  8. JDBC连接-操作数据库

    JDBC连接数据库的操作步骤 *条件:先启动mysql,然后创建新连接.这里我用Navicat工具来操作数据库. 前面是创建数据库,以及授权的问题.然后打开eclipse 这里我整理一下 抛出的两个异 ...

  9. cve-2018-2893 WebLogic

    最近爆出来了新的漏洞cve-2018-2893 一.背景介绍 WebLogic是美国Oracle公司出品的一个Application Server,确切的说是一个基于JAVAEE架构的中间件,WebL ...

  10. 全栈工程师知识点汇总——html5(上)

    一.HTML5 1.新特性: 1. 取消了过时的显示效果标记 <font></font> 和 <center></center> ... 2. 新表单元 ...