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 只能赋值给 ...
随机推荐
- django基础知识之验证码:
验证码 在用户注册.登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻一些服务器的压力 使用验证码也是一种有效的防止crsf的方法 验证码效果如下图: 验证码视 ...
- 就是要让你彻底学会 @Bean 注解
@Bean 注解全解析 随着SpringBoot的流行,基于注解式开发的热潮逐渐覆盖了基于XML纯配置的开发,而作为Spring中最核心的bean当然也能够使用注解的方式进行表示.所以本篇就来详细的讨 ...
- 基于缓存或zookeeper的分布式锁实现
缓存锁 我们常常将缓存作为分布式锁的解决方案,但是却不能单纯的判断某个 key 是否存在 来作为锁的获得依据,因为无论是 exists 和 get 命名都不是线程安全的,都无法保证只有一个线程可以获 ...
- 给即将或刚进入it行业的朋友的参考--体育生转行到Java的经历
首先声明,不喜勿喷我这个历经千辛万苦转入Java行业的人.相信很多正在学校中迷茫和在毕业后对于现状不满意的朋友通过各种的渠道进行信息汇总,了解了软件行业的高工资和多机会,正摩拳擦掌准备在这个领域大干一 ...
- springcloud高可用方案
1. 场景描述 公司在规划后续可能会做中台服务,考虑用微服务的方案,让用springcloud部署个简单的高可用Demo. 2. 解决方案 2.1 方案说明 demo用了5台虚拟机: (1)1台gat ...
- mybatis学习(五)(动态mybatis(多条件查询))
有时候要查询条件是多条件的,尤其是使用mybatis的时候如何创建sql语句呢? 这里mybatis有自己的办法,如下: 案例:通过传入map,根据map里面的数据来查询 mapper配置如下: &l ...
- py+selenium+IE10【IE已停止工作】【已解决】
问题:跑自动化时,到某个用例IE就崩,提示已停止工作. 手工跑的时候,IE挂,提示“Internet Explorer 已经为了帮助保护您的计算机而关闭此网页”. 且每次都在需要调用flash插件 ...
- EF简介及CRUD简单DEMO
一.实体框架(Entity FrameWork)简介 • 简称EF • 与Asp.Net MVC关系与ADO.NET关系 • ADO.NET Entity FrameWork是微软以ADO.NET为基 ...
- HelloDjango 启动!免费带你学Django全栈!
欢迎 追梦 入伙 HelloGitHub-Team,同时为我们带来了完全免费的 HelloDjango 系列教程,全网首发于 HelloGitHub 公众号.让想你的系列文章被跟多人看到,那就来加入我 ...
- 【HDOJ】2007平方和与立方和
Problem Description 给定一段连续的整数,求出他们中所有偶数的平方和以及所有奇数的立方和. Input 输入数据包含多组测试实例,每组测试实例包含一行,由两个整数m和n组成. ...