Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24139 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

14 | 接口类型的合理运用

郝林 2018-09-12
你好,我是郝林,今天我们来聊聊接口的相关内容。

前导内容:正确使用接口的基础知识

在 Go 语言的语境中,当我们在谈论“接口”的时候,一定指的是接口类型。因为接口类型与其他数据类型不同,它是没法被实例化的。
更具体地说,我们既不能通过调用new函数或make函数创建出一个接口类型的值,也无法用字面量来表示一个接口类型的值。
对于某一个接口类型来说,如果没有任何数据类型可以作为它的实现,那么该接口的值就不可能存在。
我已经在前面展示过,通过关键字typeinterface,我们可以声明出接口类型。
接口类型的类型字面量与结构体类型的看起来有些相似,它们都用花括号包裹一些核心信息。只不过,结构体类型包裹的是它的字段声明,而接口类型包裹的是它的方法定义。
这里你要注意的是:接口类型声明中的这些方法所代表的就是该接口的方法集合。一个接口的方法集合就是它的全部特征。
对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征(即全部的方法),那么它就一定是这个接口的实现类型。比如下面这样:
type Pet interface {
SetName(name string)
Name() string
Category() string
}
我声明了一个接口类型Pet,它包含了 3 个方法定义,方法名称分别为SetNameNameCategory。这 3 个方法共同组成了接口类型Pet的方法集合。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(40)

  • hiyanxu 置顶
    老师,您好:
    我在这篇文章中看到您说,给接口类型变量赋值时传递的都是副本,我测试了,确实是不会改变被赋值后的接口类型变量。
    后面,我重新给Pet接口加上了SetName()方法,然后让*Dog类型实现了该Pet接口,然后声明并初始化了一个d,将d的地址&d赋值给Pet类型的接口变量:
    d := Dog{name: "little dog"}
    var pet Pet = &d
    此时,我去修改了d的name字段:
    d.SetName("big dog")
    运行后发现输出不仅d的name字段变为了“big dog”,同样pet接口变量也变成了“big dog”。
    在此时我是不是可以说,传递给pet变量的同样是&d的一个指针副本,因为传递的是副本,所以无论是指针还是值,都可以说是浅复制;且由于传递的是指针(虽然是副本),但还是会对指向的底层变量做修改。
    请问老师,我上面的推断是正确的吗?
    另外我想说真的每篇文章都需要好好研读啊,看一篇得两个小时,里面好多干货,谢谢老师!

    作者回复: 没错,虽然是副本,但却是指针的副本,SetName又是指针方法。所以综合起来这种修改就生效了。

    另外这类副本都是浅表复制。也没错。

    2018-12-19
    8
  • xlh
    大神,每篇文章前能先解答上次留的问题吗?思考过后有个答案,有错思之,无错加勉
    2018-09-13
    57
  • extraterrestrial!!
    有个疑问,go里面一个类型实现了接口所有的方法,才算该接口类型,但并没有语法显式说明这个类型实现了哪个接口(例如java中有implements), 这样看别人代码的时候,碰到一个类型,无法知道这个类型是不是实现了一个接口,除非类型和接口写在一个文件,然后还要自己一个一个方法去对比。有比较快的方法可以知道当前类型实现了哪些接口么?
    2018-09-16
    2
    30
  • 追梦
    关于思考题,如果我们把一个值为nil的某个实现类型的变量赋给了接口变量,在这个接口变量上仍然可以访问其方法,但无法访问其属性。使用时需要注意:如果涉及到变量属性,这些属性值均为默认值。
    2018-09-17
    11
  • Aaron
    文章中demo32.go demo31.go可不可以直接贴出来
    2018-09-20
    9
  • asdf100
    golang中,结构体类型struct包裹的是它的字段声明,而接口类型interface包裹的是它的方法定义。
    2018-09-12
    7
  • undifined
    因为将 nil 实际类型的变量赋值给接口变量,会包装为 iface 实例,这个实例不为空,所以依然可以调用接口的方法,但是通过方法访问变量的属性,则会返回空
    2018-09-12
    5
  • 吉他
    关于思考题,如果我们把一个值为nil的某个实现类型的变量赋给了接口变量,那么在这个接口变量上仍然可以调用该接口的方法吗?
    可以的,不过方法内不能使用实现类型内的变量,并且方法接收者必须是指针类型

    作者回复: 对的,对于指针方法来说是这样。

    2019-05-25
    4
  • 志鑫
    思考题,要看实现类型是值类型还是指针类型;
    var d2 Dog
    var p2 Pet = d2
    if p2 != nil {
    fmt.Println("p2.Name()", p2.Name())
    }

    var d3 *Dog
    var p3 Pet = d3
    if p3 != nil {
    fmt.Println("p3.Name()", p3.Name()) //运行是报错,panic: value method main.Dog.Name called using nil *Dog pointer

    }

    作者回复: 这主要是因为 Dog 和 *Dog 的零值是完全不同的,前者是 Dog{} ,而后者是 nil 。

    2019-05-10
    4
  • colben
    package main

    import (
        "fmt"
    )

    type Person struct {
        name string
        age uint8
    }

    // use computer
    type ComputerOper interface {
        TurnOnComputer()
        TurnOffComputer()
    }

    // use chrome
    type ChromeOper interface {
        ComputerOper
        TurnOnChrome()
        TurnOffChrome()
    }

    // turn on computer
    func (p *Person) TurnOnComputer() {
        fmt.Println("Power on computer and boot OS.")
    }

    // turn off computer
    func (p Person) TurnOffComputer() {
        fmt.Println("Shutdown OS and power off computer.")
    }

    // start chrome
    func (p *Person) TurnOnChrome() {
        fmt.Println("Start web browser chrome.")
    }

    // stop chrome
    func (p Person) TurnOffChrome() {
        fmt.Println("Stop web browser chrome.")
    }

    // someone use chrome
    func UseChrome(user ChromeOper) {
        user.TurnOnComputer()
        user.TurnOnChrome()
    // 下面两个是"值方法", 空指针panic
    // user.TurnOffChrome()
    // user.TurnOffComputer()
    }

    func main() {
        var user *Person
        UseChrome(user)
    }

    // 变量本身肯定读写不了,值方法在调用时要复制一个副本,所以也用不了,就剩下指针方法了。
    2018-09-13
    4
  • 俊杰
    老师您好,有个地方不理解,对象赋值给接口后,为什么判等操作返回的是true呢?比如上面的例子:pet = dog之后紧接着判断pet == dog,返回的是true,按上面的说法,赋值后不是应该被包装成了一个iface吗?这里的判等操作到底是依据什么来判断的呢?麻烦老师解释一下,谢谢~

    作者回复: 你可以参照Go语言规范中的说明:https://golang.google.cn/ref/spec#Comparison_operators,请注意下面这句:

    A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.

    2019-03-20
    1
    2
  • Arthur.Li
    方法签名在这里是指什么呀?我看定义说是方法名称和一个参数列表(方法的参数的顺序和类型)组成。
    文章里面写的两个条件是方法签名一致和方法名一致,所以有些疑惑了

    作者回复: 方法签名严格来说不包含方法名。

    2018-09-21
    2
  • Michael
    这结课终于让我明白了 reflect 包中,reflect.Type 和 reflect.Value 存在的意义,茅塞顿开啊!

    接口变量的值并不等同于这个可被称为动态值的副本。它会包含两个指针,一个指向动态值,一个指向动态类型。

    关于思考题,我试验之后是不可以的,因为在调用的时候没有接收者,还请老师指正
    2018-09-17
    2
  • 兴小狸
    接口中声明多个方法,有的方法有返回值,有的是没有的。当一个类实现这些接口时,要怎么知道是传值接收者,还是指针接收者呢?

    作者回复: 这与方法有没有返回值没有关系啊。值方法还是指针方法应该是类型定义者考虑的事情。作为使用者,你可以先假定方法的定义是正确的(可以使程序正常工作)。

    回到你最后的问题。你可以看源码,也可以用 reflect 包里的 API 进行探查(不过比较麻烦)。不过我觉得通常没必要这么做。

    2019-08-03
    1
  • 枫林火山
    老师,您好,在demo34.go 中我本意是想尝试用interface做内嵌字段来显式表明一个struct的接口能力的。 但是这过程中发现,如果我在Dog中内嵌了Animal接口,然后注释掉Dog的ScientificName实现,line37 - 45 如下
    type Dog struct {
    Animal
    PetTag
    scientificName string
    }

    // func (dog Dog) ScientificName() string {
    // return dog.scientificName
    // }
    运行代码结果依然是
    PetTag implements interface Named: true
    Dog implements interface Animal: true
    Dog implements interface Named: true
    Dog implements interface Pet: true
    老师能否讲解下这是Go语言的静态检查不完善还是别有深意,我这样使用接口内嵌来表明一个Struct的能力,这样是不是有问题,正确的声明方式是什么?

    作者回复: 郝林回复:首先,你要明白在Dog里内嵌Animal意味着什么。这意味着Dog类型中包含了一个Animal类型的匿名字段。正是由于这个匿名字段,Dog类型就包含了实现Animal接口所需的所有方法,只不过你并没有为这个匿名字段赋值而已。但是这个字段就摆在那儿了,匿名字段Animal的方法照样会融入Dog的方法集合。这是一个不争的事实,所以类型判断的时候照样会返回true。实际上,单从方法集合融入的这个方面讲,这与Dog内嵌PetTag是一样的。

    这样的嵌入方式是可以的。当你要做部件动态装配的时候可以这么做,不论是嵌入接口类型还是嵌入非接口类型。当然了,内嵌接口类型会更加灵活一些,因为你可以为这个匿名字段赋予不同的实现值。

    2019-03-28
    1
  • Garry
    思考题:
    可以通过接口变量调用,但需要注意实现的方法中不能出现访问类型属性的操作,会报空指针解引用错误。
    2018-12-11
    1
  • Geek_1b0d68
    由于把值为nil的变量赋值给了接口变量,接口变量在调用时只能调用该实现类型的指针方法,而无法调用值方法。为什么值方法只能实现了该类型的对象或者对象指针才能够调用?
    2018-09-18
    1
  • 兔子高
    你好,请问分别在什么情况下使用值方法和什么情况下使用引用方法呢

    作者回复: 需要改动值内部数据的时候必须使用指针方法。其他时候就要看接口实现、被嵌入要求等方面了。我觉得一般情况下用指针方法就好了。

    2018-09-12
    1
  • 疯琴
    讲得太精彩了,如饮醍醐。
    2019-11-28
  • redrain
    代码都在github上吗,手机看文章的时候最好还是把代码直接贴出来

    作者回复: 当然,在这里:https://github.com/hyper0x/Golang_Puzzlers

    2019-09-17
收起评论
40
返回
顶部