1、Go如何定义interface

  Go通过type声明一个接口,形如

type geometry interface {
area() float64
perim() float64
}

  和声明一个结构体一样,接口也是通过type声明。

  type后面是接口名称,紧挨着是关键字interface。

  接口里面定义的area()和perim是接口geometry的方法。

  有了接口,那应该如何实现接口呢?

type rect struct {
width, height float64
} func (r rect) area() float64 {
return r*width*height
} func (r rect) perim() float64 {
return 2*r*width + 2*r*height
}

  上面就是rect实现接口geometry的代码。不同于Java这些语言,有显式的关键字如implement表示实现某个接口。

  和Java接口的契约精神有些不同的是,Go里面的接口实现更像是组合的概念。

  这里要提一个”鸭子类型“的概念。鸭子类型是动态编程语言的一种对象推断策略,它更关注对象能如何被使用,而不是对象的类型本身。即一个东西如果长得像鸭子,会像鸭子一样嘎嘎叫、走路、游泳,那么我们就可以推断这个小东西就是鸭子。

  类比上面的代码,rect就是长得像鸭子geometry的,可以像geometry一样的area()行为,也可以像geometry一样的perim(),rect满足了geometry定义的一切行为,所以我们推断rect就是实现了接口geometry的。

  这样,我们不用再去写implement xxx这样的代码了。由原来一个类的粒度细化到类里面方法的粒度了。

  顺便提一句,之前在做Java开发的时候,由于是协同开发,都是用统一的框架,加上面向接口编程的思想深入人心,以至于成为这样的一种条件反射:在写一个service的时候,第一反应是新建一个接口,然后定义接口中方法,之后再是编写实现类,绝大多数情况,都是只会用到这一个实现类,未来很长时间都没有看到这个接口的其他实现类。这种为了实现接口而编写接口,有时候在中小型项目中让代码显得很死板。

2、如何判定是否是某个interface的实现

  上面我们介绍了Go是如何定义一个接口并”实现“接口的。上面代码只有一个rect结构体,如果有多个呢

type rect struct {
width, height float64
} func (r rect) area() float64 {
return r*width*height
} func (r rect) perim() float64 {
return 2*r*width + 2*r*height
} type circle struct {
radius float64
} func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}

  对于这种情况,我们总不能一个个肉眼比对,看看rect、circle是否实现了geometry中定义的所有方法吧

  Go可以通过类型断言来判定。

func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5} measure(r)
measure(c) var g geometry
g = circle{radius:10}
switch t := g.(type) {
case circle:
fmt.Println("circle type", t)
case rect:
fmt.Println("rect type", t)
}
}

  

  执行结果为

{3 4}
12
14
{5}
78.53981633974483
31.41592653589793
circle type {10}

  可以看出,Go可以推断g是实现了geometry接口的circle。

  类型断言的语法为

<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言

<目标类型的值> := <表达式>.( 目标类型 )  //非安全类型断言

  上面的写法是switch语法,即第二种。第一种举例如下

var g geometry
if f, ok := g.(circle); ok {
fmt.Println("circle type", f)
}

  

3、值接收还是指针接收

  在Go中一个方法,我们可以定义一个方法是用某个struct的值来接收还是指针接收,形如

type rect struct {
width, height int
} func (r *rect) area() int {
return r.width * r.height
} func (r rect) perim() int {
return 2*r.width + 2*r.height
} func main() {
r := rect{width: 10, height: 20}
fmt.Println("area:", r.area())
fmt.Println("perim:", r.perim()) rp := &r
fmt.Println("area:", rp.area())
fmt.Println("perim:", rp.perim())
}

  这里定义结构体rect,同时定义两个方法area()和perim()。在这两个方法左边定义的即为方法的接收者,其中area()由rect的指针类型接收,perim()则由rect值类型接收。

  这样表示area()和perim()是rect的两个方法。从代码我们可以看出,该种形式不管是传入值类型还是传入rect的指针,执行都正常返回结果。

area: 200
perim: 60
area: 200
perim: 60

  

对于r.area()可以调通的背后Go做了什么?

  此时r是一个值类型,为了实现调用即使是指针接收类型的area()方法,Go实际是先找到r的地址,然后通过一个指针指向它,即r.area()转化成了(&r).area(),从而满足了area()方法是指针接收者的约束。

对于rp.perim()可以调通的背后Go做了什么?

  此时rp是一个指针类型。在调用时,指针被解引用为值,这样便符合perim()方法定义的接收者类型的约束。解引用的过程我们可以认为Go把rp.perim()转化为于(*rp).perim()。但是注意perim()方法是值接收类型,所以操作的是rect的副本。

  所以,综上,对于普通方法的调用,不管接收者是值类型还是指针类型,调用者是值类型还是指针类型,都可以调通。

  上面是针对纯粹的方法而言的,如果在接口的背景下,情况是否一致呢?

type geometry interface {
area() float64
perim() float64
} type rect struct {
width, height float64
} type circle struct {
radius float64
} func (r rect) area() float64 {
return r.width * r.height
} func (r rect) perim() float64 {
return 2*r.width + 2*r.height
} func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
} func (c *circle) perim() float64 {
return 2 * math.Pi * c.radius
} func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
} func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5} measure(r)
measure(c)
}

  

  这段的代码与上面的唯一不同的地方在于将perim接收者类型由circle改为了*circle类型,导致在运行程序时报错

# command-line-arguments
main/src/examples/interfaces.go:48:9: cannot use c (type circle) as type geometry in argument to measure:
circle does not implement geometry (perim method has pointer receiver)

  意思是说circle没有实现geometry接口。

  如果反过来

type geometry interface {
area() float64
perim() float64
} type rect struct {
width, height float64
} type circle struct {
radius float64
} func (r rect) area() float64 {
return r.width * r.height
} func (r rect) perim() float64 {
return 2*r.width + 2*r.height
} func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
} func (c *circle) perim() float64 {
return 2 * math.Pi * c.radius
} func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
} func main() {
r := &rects1{width: 3, height: 4}
c := &circle{radius: 5} measure(r)
measure(c)
}

  此时调用一切正常。

  所以对比看下来发现,对于值接收者,传如值或者指针都可以正常调用;对于指针接收者,则只能传入指针类型,否则会报未实现接口的错误。

关于原理,我看了很多说法
说法一

"对于指针类型,Go会自动转换,因为有了指针总是能得到指针指向的值是什么,如果是 value 调用,go 将无从得知 value 的原始值是什么,因为 value 是份拷贝。go 会把指针进行隐式转换得到 value,但反过来则不行。  "

说法二

"当实现一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。"

  但是这两种说法我觉得还是没有真正说到原理上,也可能是我没有理解。

  在前面不涉及到接口的单纯方法的值接收者和指针接收者,使用值或者指针调用都是可以的,因为Go会在底层做这个类型转换。但是在接口这个背景下,如果方法有指针类型接收类型,则只能传指针类型,可能还是和Go的接口底层实现有关。如果大家有自己的理解,欢迎指教。

今天主要介绍了Go语言中的接口的定义和实现以及如何使用,还有一些小知识点比如空interface的作用和使用就不再赘述。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Go语言入门——interface的更多相关文章

  1. PBFT概念与Go语言入门(Tendermint基础)

    Tendermint作为当前最知名且实用的PBFT框架,网上资料并不很多,而实现Tendermint和以太坊的Go语言,由于相对小众,也存在资料匮乏和模糊错漏的问题.本文简单介绍PBFT概念和Go语言 ...

  2. 一.OC基础之:1,OC语言的前世今生 ,2,OC语言入门,3,OC语言与C的差异,4,面向对象,5,类和对象的抽象关系,6,类的代码创建,7,类的成员组成及访问

    1,OC语言的前世今生 , 一, 在20世纪80年代早期,布莱德.麦克(Brad Cox)设计了OC语言,它在C语言的基础上增加了一层,这意味着对C进行了扩展,从而创造出一门新的程序设计语言,支持对象 ...

  3. Go 语言入门(二)方法和接口

    写在前面 在学习 Go 语言之前,我自己是有一定的 Java 和 C++ 基础的,这篇文章主要是基于A tour of Go编写的,主要是希望记录一下自己的学习历程,加深自己的理解 Go 语言入门(二 ...

  4. 《JavaScript语言入门教程》记录整理:面向对象

    目录 面向对象编程 实例对象与 new 命令 this关键字 对象的继承 Object对象的方法 严格模式(strict mode) 本系列基于阮一峰老师的<JavaScrip语言入门教程> ...

  5. 【Go语言入门系列】(八)Go语言是不是面向对象语言?

    [Go语言入门系列]前面的文章: [Go语言入门系列](五)指针和结构体的使用 [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? 1. Go是面向对象的语言吗? 在[ ...

  6. 【Go语言入门系列】(九)写这些就是为了搞懂怎么用接口

    [Go语言入门系列]前面的文章: [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? [Go语言入门系列](八)Go语言是不是面向对象语言? 1. 引入例子 如果你使用 ...

  7. 浅析Go语言的Interface机制

    前几日一朋友在学GO,问了我一些interface机制的问题.试着解释发现自己也不是太清楚,所以今天下午特意查了资料和阅读GO的源码(基于go1.4),整理出了此文.如果有错误的地方还望指正. GO语 ...

  8. 踢爆IT劣书出版黑幕——由清华大学出版社之《C语言入门很简单》想到的(1)

    1.前言与作者 首先声明,我是由于非常偶然的机会获得<C语言入门很简单>这本书的,绝对不是买的.买这种书实在丢不起那人. 去年这书刚出版时,在CU论坛举行试读推广,我当时随口说了几句(没说 ...

  9. 我为什么反对推荐新人编程C/C++语言入门?

    虽然我接触编程以及计算机时间比较早,但是正式打算转入程序员这个行当差不多是大学第四年的事情 从03年接触计算机,07年开始接触计算机编程, 期间接触过的技术包括 缓冲区溢出(看高手写的shellcod ...

随机推荐

  1. 【java异常】定时任务异常ERROR 20604 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task

    2019-11-20 13:20:00.006 ERROR 20604 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unex ...

  2. Excel-自定义单元格、填充柄

    数据分析是指用适当的统计分析方法对收集来的大量数据进行分析, 提取有用信息和形成结论而对数据加以详细研究和概括总结的过程. 工具:EXCEL,sql,SAS,SPSS,R,Python.Hadoop. ...

  3. 关于时间排序在ios中失效的处理方法

    上个月公司做项目的时候在列表排序的时候产品加了一个需求,通过点击量,发布时间,评论量进行筛选的一个需求. 一开始在电脑上测试基本没问题,然后我也就放下了这个按耐不住的小心脏,然后在完成所有模块后 sh ...

  4. [PHP] Laravel使用redis保存SESSION

    Laravel使用redis保存SESSION 首先确认服务器已经安装redis服务,php安装了redis扩展. 1.打开config/database.php.在redis配置项中增加sessio ...

  5. 转载:tensorflow保存训练后的模型

    训练完一个模型后,为了以后重复使用,通常我们需要对模型的结果进行保存.如果用Tensorflow去实现神经网络,所要保存的就是神经网络中的各项权重值.建议可以使用Saver类保存和加载模型的结果. 1 ...

  6. Kdtree原理以及 vs Octree

    1. Kdtree原理 Kdtree是一种划分k维数据空间的数据结构,本质也是一颗二叉树,只不过每个节点的数据都是k维,当k=1时,就是图1所示的普通二叉树. 图1 1)Kdtree的建立 建立Kdt ...

  7. pytest 学习笔记一 入门篇

    前言 之前做自动化测试的时候,用的测试框架为Python自带的unittest框架,随着工作的深入,发现了另外一个框架就是pytest (官方地址文档http://www.pytest.org/en/ ...

  8. 【07月01日】A股滚动市净率PB历史新低排名

    2010年01月01日 到 2019年07月01日 之间,滚动市净率历史新低排名. 上市三年以上的公司,2019年07月01日市净率在30以下的公司. 来源:A股滚动市净率(PB)历史新低排名. 1 ...

  9. 关于JavaSE程序的小总结(不分先后顺序 后续继续补充)

    统计字符串中某个字符串出现的次数 package com.jiang.demo01; public class Demo01 { public static void main(String[] ar ...

  10. Python【每日一问】25

    问: [基础题]:利用条件运算符的嵌套来完成此题:学习成绩 >=90 分的同学用 A 表示,60-89 分之间的用 B 表示,60 分以下的用 C 表示. [提高题]:求 s=a + aa + ...