13.Go语言-接口

2022-09-04 10:49:53 浏览数 (1)

第 13 章

在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型实现了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。

13.1 接口的定义

使用 type 关键字可以定义接口:

代码语言:go复制
type interface_name interface {
    method()
}

13.2 接口的实现

创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:

代码语言:go复制
package main

import "fmt"

type Study interface {
    learn()
}

type Student struct {
    name string
}

func (s Student) learn() {
    fmt.Printf("%s 在读 %s", s.name, s.book)
}

func main() {
    student1 := Student{
        name: "张三",
        book: "《Go语言极简一本通》",
    }
    student1.learn()
}

上面的程序定义了一个名为 Study 的接口,接口中有未实现的方法 learn() ,这里还定义了名为 Student 的结构体,其绑定了方法 learn() ,也就隐式实现了 Study 接口,实现的内容是打印语句。

上面的例子使用了值接受者实现接口,下面的例子使用了指针接受者实现接口。

代码语言:go复制
package main

import "fmt"

type Study interface {
    learn()
}

...

type Worker struct {
    name string
    book string
    by   string
}

func (w *Worker) learn() {
    fmt.Printf("%s 在读 %s,通过方式 %s", w.name, w.book, w.by)
}

func main() {
    var s1 Study
    var s2 Study

    student2 := Student{
        name: "李四",
        book: "《Go语言极简一本通》",
    }
    s1 = student2
    s1.learn()

    student3 := Student{
        name: "王五",
        book: "Go语言微服务架构核心22讲",
    }
    s1 = &student3
    s1.learn()

    worker1 := Worker{
        name: "老王",
        book: "从0到Go语言微服务架构师",
        by:   "视频",
    }
    // s2 = worker1 // error
    s2 = &worker1
    s2.learn()
}

该程序定义了结构体 Student ,使用其作为值接受者实现 Study 接口。student2 的类型为 Studentstudent2 赋值给 s1 ,由于 Student 实现了接口变量 s1 所以会有输出。而接下来 s1 又被赋值为 &student3 ,同样有输出。接下来的结构体 Worker 使用指针接受者实现 Study 接口。worker1 的类型为 Workers2 被赋值为 &worker1 ,所以会有输出。但如果把 s2 赋值为 worker1 会报错,对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此对于编译器无法自动获取 worker1 的地址,于是程序报错。

13.3 接口实现多态

使用接口可以实现多态,例如下面的程序,定义了名为 Study 的接口,接口中有方法 learn() 。程序中还定义了结构体 StudentWorker ,分别实现了 Study 接口,Student 的 learn name: "李四", book: "《Go语言极简一本通》" 而 Worker 的 learn 为 name: "张三",book: "从0到Go语言微服务架构师",by: "视频" ,利用的接口实现了不同的功能,这就是多态。

代码语言:go复制
package main

func main() {
    ...
    s2.learn()
    worker1.learn()
}

13.4 接口的内部表示

可以把接口的内部看做 (type, value)type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。

代码语言:go复制
package main

import "fmt"

...

func ShowInterface(s Study) {
    fmt.Printf("接口类型: %Tn,接口值: %vn", s, s)
}

func main() {
    var s Study
    s = student2
    ShowInterface(s)
    s.learn()
}

在上面的程序中,定义了 Study 接口,其中有 learn() 方法,结构体 Student 实现了该接口。使用 s = student2 语句我们把 student2 ( Student 类型)赋值给了 s ( Study 类型),现在打印出 Study 的具体类型为 Student ,而 student2 的值为 name: "李四", book: "《Go语言极简一本通》"

13.5 空接口

空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 interface{} 。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:

代码语言:go复制
package main

import "fmt"

func ShowType(i interface{}) {
    fmt.Printf("类型: %T, 值: %vn", i, i)
}

func main() {
    str := "从0到Go语言微服务架构师"
    ShowType(str)
    num := 3.14
    ShowType(num)
}

上面的程序中我们定义了函数 ShowType 使用空接口作为参数,所以可以给这个函数传递任何类型的参数。

通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 nil

代码语言:go复制
package main

import "fmt"

func main() {
    var i interface{}
    fmt.Printf("Type: %T, Value: %v", i, i)
    // Type: <nil>, Value: <nil>
}

除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。

直接使用 interface{} 作为类型声明一个实例,这个实例就能承载任何类型的值:

代码语言:go复制
package main

import "fmt"

func main() {
    var i interface{}

    i = "从0到Go语言微服务架构师"
    fmt.Println(i) // Let's go

    i = 3.14
    fmt.Println(i) // 3.14
}

我们也可以定义一个接收任何类型的 arrayslicemapstrcut 。例如:

代码语言:go复制
package main

import "fmt"

func main() {
    x := make([]interface{}, 3)
    x[0] = "从0到Go语言微服务架构师"
    x[1] = 3.14
    x[2] = []int{1, 2, 3}
    for _, value := range x {
        fmt.Println(value)
    }
}

空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。

代码语言:go复制
package main

func main() {
    var num = 1
    var i interface{} = num
    var str string = i // error
}

当空接口承载数组和切片后,该对象无法再进行切片。

代码语言:go复制
package main

import "fmt"

func main() {
    var s = []int{1, 2, 3}

    var i interface{} = s

    var s2 = i[1:2] // error
    fmt.Println(s2)
}

13.6 类型断言

类型断言用于提取接口的底层值(Underlying Value)。使用 interface.(Type) 可以获取接口的底层值,其中接口 interface 的具体类型是 Type

代码语言:go复制
package main

import "fmt"

func assert(i interface{}) {
    value, ok := i.(int)
    fmt.Println(value, ok)
}

func main() {
    var x interface{} = 3
    assert(x)
    var y interface{} = "从0到Go语言微服务架构师"
    assert(y)
}

13.7 类型选择

类型选择用于将接口的具体类型与 case 语句所指定的类型进行比较。它其实就是一个 switch 语句,但在 switch 后面跟的是 i.(type) ,并且每个 case 后面跟的是类型。

代码语言:go复制
package main

import "fmt"

func getTypeValue(i interface{}) {
    switch i.(type) {
    case int:
        fmt.Printf("Type: int, Value: %dn", i.(int))
    case string:
        fmt.Printf("Type: string, Value: %sn", i.(string))
    default:
        fmt.Printf("Unknown typen")
    }
}

func main() {
    getTypeValue(300)
    getTypeValue("从0到Go语言微服务架构师")
    getTypeValue(true)
}

13.8 实现多个接口

类型或者结构体可以实现多个接口,例如:

代码语言:go复制
package main

import "fmt"

...


type Happy interface {
	rest()
}

func (s Student) rest() {
    fmt.Printf("%s 放学了,出去玩...", s.name)
}

func (w *Worker) rest() {
    fmt.Printf("%s 下班了,吃大餐去...", w.name)
}

func main() {
    worker2 := Worker{
        name: "小明",
        book: "从0到Go语言微服务架构师",
        by:   "视频",
    }
    worker2.learn()
    worker2.rest()
}

13.9 接口的嵌套

虽然在 Go 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:

代码语言:go复制
package main

import "fmt"

...

type Life interface {
    Study
    Happy
}

func main() {
    worker2 := Worker{
        name: "小明",
        book: "从0到Go语言微服务架构师",
        by:   "视频",
    }
    worker2.learn()
    worker2.rest()
}

0 人点赞