学过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. POJ 2728:Desert King(最优比率生成树)

    http://poj.org/problem?id=2728 题意:有n个点,有三个属性代表每个点在平面上的位置,和它的高度.点与点之间有一个花费:两点的高度差:还有一个长度:两点的距离.现在要让你在 ...

  2. os.path.join路径拼接的问题

    问题一: import os a = os.path.join("/test1", "/test2") print(a) b = os.path.join(&q ...

  3. elk 系列:Elasticsearch 7.2 集群部署+TLS 加密+认证登陆

    背景 2019年5月21日,Elastic官方发布消息: Elastic Stack 新版本6.8.0 和7.1.0的核心安全功能现免费提供. 这意味着用户现在能够对网络流量进行加密.创建和管理用户. ...

  4. python 多个装饰器的调用顺序

    python 多个装饰器的调用顺序 一般情况下,在函数中可以使用一个装饰器,但是有时也会有两个或两个以上的装饰器.多个装饰器装饰的顺序是从里到外(就近原则),而调用的顺序是从外到里(就远原则). 原代 ...

  5. centos 7 安装docker,conflicts 异常

    [root@localhost html]# yum install docker-io 已加载插件:fastestmirror, langpacks Loading mirror speeds fr ...

  6. android网络编程_socket(一)

    转载http://www.eoeandroid.com/thread-97477-1-1.html 小知识点:UDP协议和TCP协议的不同.UDP是把数据都打成数据包,数据包上自带通信的地址,但是数据 ...

  7. spark 源码分析之十五 -- Spark内存管理剖析

    本篇文章主要剖析Spark的内存管理体系. 在上篇文章 spark 源码分析之十四 -- broadcast 是如何实现的?中对存储相关的内容没有做过多的剖析,下面计划先剖析Spark的内存机制,进而 ...

  8. 基于Vue的通用框架Nuxt.js

    问题:最近有些朋友问我写官网,用Vue框架实现好不好? 相信很多使用Vue的小伙伴们一样疑惑这个问题,其实从这个问题就可以知道你对Vue这个框架是否真的熟悉了.其实单单使用Vue这个框架来做官网的,其 ...

  9. MyBatis 存储过程

    From<MyBatis从入门到精通> <!-- 6.2 存储过程 6.2.1 第一个存储过程 delimiter ;; create procedure 'select_user_ ...

  10. [原创]实现MongoDB数据库审计SQL语句的脚本

    功能:实现具体显示mongodb数据库表操作语句的状态和情况,使用awk和shell实现抓取和分析处理.脚本内容如下: #!/bin/bash if [ $# == 0 ];then echo &qu ...