【go语言学习】接口interface

面向对象世界中的接口的一般定义是“接口定义对象的行为”。它表示让指定对象应该做什么。实现这种行为的方法(实现细节)是针对对象的。

在Go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

在go语言中,接口和类型的实现关系,是非侵入式的。

一、接口的定义和使用

1、interface的定义
type interfaceName interface {
        methodName(parameter list) (return list)
        methodName(parameter list) (return list)
        ...
        methodName(parameter list) (return list)
}

其中:

  • interfaceName接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • methodName方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • parameter list参数列表、return list返回值列表:参数列表和返回值列表中的参数变量名可以省略。
package main

import "fmt"

// Person 接口
type Preson interface {
    Say()
}

// Student 结构体
type Student struct {
    Name    string
    Age     int
    Address string
}

// Say Student的方法
func (s Student) Say() {
    fmt.Println(s.Name, "说:hello!")
}

func main() {
    s := Student{"tom", 20, "苏州市"}
    s.Say()
}

运行结果

tom 说:hello!

Student实现了接口Person中的所有方法,那么就可以说Student实现了Person接口。

2、接口类型

接口类型变量能够存储所有实现了该接口的实例。

  • 当需要接口类型的对象时,可以使用任意实现类对象代替
  • 接口对象不能访问实现类中的属性
package main

import "fmt"

// USB 接口
type USB interface {
    start()
    end()
}

//Mouse 鼠标结构体
type Mouse struct {
    name string
}

// FlashDisk U盘结构体
type FlashDisk struct {
    name string
}

// start Mouse的方法
func (m Mouse) start() {
    fmt.Println(m.name, "鼠标开始工作了。。。")
}

// end Mouse的方法
func (m Mouse) end() {
    fmt.Println(m.name, "鼠标结束工作了。。。")
}

// start FlashDisk的方法
func (f FlashDisk) start() {
    fmt.Println(f.name, "鼠标开始工作了。。。")
}

// end FlashDisk的方法
func (f FlashDisk) end() {
    fmt.Println(f.name, "鼠标结束工作了。。。")
}

// test USB测试函数
func test(u USB) {
    u.start()
    u.end()
}

func main() {
    m := Mouse{"罗技"}
    f := FlashDisk{"金士顿"}
    test(m)
    test(f)
    fmt.Println("---------------------------")
    // 定义一个USB接口类型的变量u
    var u USB
    // m实现了USB接口的所有方法,m就实现了USB接口,就可以把m赋值给u。
    u = m
    u.start()
    u.end()
    // f也实现了USB接口的所有方法,f也实现了USB接口,也可以把f赋值给u。
    u = f
    u.start()
    u.end()
}

运行结果

罗技 鼠标开始工作了。。。
罗技 鼠标结束工作了。。。
金士顿 鼠标开始工作了。。。
金士顿 鼠标结束工作了。。。
---------------------------
罗技 鼠标开始工作了。。。
罗技 鼠标结束工作了。。。
金士顿 鼠标开始工作了。。。
金士顿 鼠标结束工作了。。。
3、值接收者和指针接收者实现接口的区别
  • 值接收者实现接口
package main

import "fmt"

// Mover 接口
type Mover interface {
    move()
}

// Dog 结构体
type Dog struct {
}

// move Dog的方法
func (d Dog) move() {
    fmt.Println("dog move")
}

func main() {
    var m Mover
    wangcai := Dog{} // 旺财是Dog类型
    m = wangcai      // m可以接受Dog类型
    m.move()
    fugui := &Dog{} // 富贵是*Dog类型
    m = fugui       // m可以接受*Dog类型
    m.move()
}

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是Dog结构体还是结构体指针*Dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,Dog指针fugui内部会自动求值*fugui

  • 指针接受者实现接口
package main

import "fmt"

// Mover 接口
type Mover interface {
    move()
}

// Dog 结构体
type Dog struct {
}

// move Dog的方法
func (d *Dog) move() {
    fmt.Println("dog move")
}

func main() {
    var m Mover
    wangcai := Dog{} // 旺财是Dog类型
    // m = wangcai      // m不可以接受Dog类型
    wangcai.move()
    fugui := &Dog{} // 富贵是*Dog类型
    m = fugui       // m可以接受*Dog类型
    m.move()
}

此时实现Mover接口的是*Dog类型,所以不能给m传入Dog类型的wangcai,此时m只能存储*Dog类型的值。

4、结构嵌套
  • 一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
  • 多个不同的类型还可以实现同一接口
  • 接口与接口间可以通过嵌套创造出新的接口。
package main

import "fmt"

// Mover 接口
type Mover interface {
    move()
}

// Sayer 接口
type Sayer interface {
    say()
}

// Dog 结构体
type Dog struct {
}

// move Dog的方法
func (d Dog) move() {
    fmt.Println("dog move")
}

// say Dog的方法
func (d Dog) say() {
    fmt.Println("dog say")
}

// Cat 结构体
type Cat struct {
}

// move Cat的方法
func (c Cat) move() {
    fmt.Println("cat move")
}

// say Cat的方法
func (c Cat) say() {
    fmt.Println("cat say")
}

// Animal 接口
type Animal interface {
    Mover
    Sayer
}

func main() {
    d := Dog{}
    c := Cat{}
    var a Animal
    a = d
    a.say()
    a.move()
    a = c
    a.say()
    a.move()
}

运行结果

dog say
dog move
cat say
cat move

二、空接口

1、空接口的定义
  • 空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
  • 空接口类型的变量可以存储任意类型的变量。
var variableName interface{}
2、空接口的应用
  • 空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

package main

import "fmt"

func show(a interface{}) {
    fmt.Println(a)
}

func main() {
    a := 10
    show(a)
    b := "hello world"
    show(b)
}

运行结果

10
hello world
  • 空接口作为map的key和value

使用空接口实现可以保存任意值的字典。

package main

import "fmt"

func main() {
    m := make(map[interface{}]interface{}, 10)
    m = map[interface{}]interface{}{
        10:   "hello",
        true: 21,
        "id": 23.1,
    }
    fmt.Printf("%+v\n", m)
}

运行结果

map[true:21 10:hello id:23.1]
  • 空接口作为切片slice或数组arry的元素

使用空接口实现可以在arry和slice中保存不同类型的数据。

package main

import "fmt"

func main() {
    s := make([]interface{}, 10)
    s = []interface{}{12, true, "hello", 23.123}
    fmt.Printf("%+v\n", s)
    a := [...]interface{}{31.23, "hello world", 12, false}
    fmt.Printf("%+v\n", a)
}

运行结果

[12 true hello 23.123]
[31.23 hello world 12 false]
3、类型断言

空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

// 安全类型断言

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

//非安全类型断言

<目标类型的值> := <表达式>.( 目标类型 )

package main

import "fmt"

func main() {
    var a interface{}
    a = true
    v, ok := a.(bool)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("断言失败")
    }
}

运行结果

true

断言其实还有另一种形式,就是用在利用 switch语句判断接口的类型。每一个case会被顺序地考虑。当命中一个case 时,就会执行 case 中的语句,因此 case 语句的顺序是很重要的,因为很有可能会有多个 case匹配的情况。

package main

import "fmt"

func main() {
    var a interface{}
    a = "hello world"
    justifyType(a)
}

func justifyType(a interface{}) {
    switch v := a.(type) {
    case string:
        fmt.Println("a is string,", v)
    case int:
        fmt.Println("a is int,", v)
    case bool:
        fmt.Println("a is bool,", v)
    default:
        fmt.Println("unknow")
    }
}

运行结果

a is string, hello world

关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342