go语言学习笔记(三)——接口和多态篇

总结一下golang中,interface的基本使用和一些重点

什么是interface

在Go中,接口是一组方法签名。接口指定类型应具有的方法,类型决定如何实现这些方法。

当用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。对接口值方法的调用会执行接口值里储存的用户定义的类型的值对应的方法。因为任何用户定义的类型都可以实现任何接口,因此对接口值方法的调用就是一种多态

使用接口的例子

package main

import "fmt"

type notifier interface {
    notify()
}

type notifier2 interface {
    notify()
}

type user struct {
    name string
    email string
}

type user2 struct {
    name string
    email string
}

func (u *user) notify() {
    fmt.Printf("%s, %s\n", u.name, u.email)
}

func (u user2) notify() {
    fmt.Println(u.name, u.email)
}

func main() {
    u := user{"Bill", "123321@qq.com"}
    fmt.Println(u)
    var noti notifier
    noti = &u
    u.notify() // 实体类型调用notify方法
    noti.notify() // 接口类型调用方法
    var noti2 notifier2
    u2 := user2{"Edison", "627927897@qq.com"}
    noti2 = u2
    noti2.notify() // 接口类型,值调用方法
    noti2 = &u2
    noti2.notify() // 接口类型,指针调用方法
}

输出如下

Bill, 123321@qq.com
Bill, 123321@qq.com
Edison 627927897@qq.com
Edison 627927897@qq.com

方法集的定义

上述例子中,体现了接口的基本用法。使用接口notifier的时候,需要将实体类型的指针赋给接口类型,但是使用接口notifier2的时候却可以将实体类型的值或是指针都赋给接口类型。

这就是方法集在实现时的要求。方法集定义了一组关联到给定类型的值或者指针的方法,定义方法时使用的接受者的类型决定了这个方法是关联到值,还是关联到指针,还是两个都关联

规则中,从接收者的类型来看方法集,可以得到下面的总结:

Methods ReceiversValues
(t T)T and *T
(t *T)*T

从上面的表格可以看到,如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能实现对于的接口;如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口。

那么选择哪种定义就可以得出结果了。如果涉及到对结构体内部属性的值的修改,就用指针接收者来实现接口。如果需要保证结构体内部属性的属性值不被改变,就可以使用值接收者来实现接口。通常情况下,用指针接收者来实现更为合适。

多态的一种实现

用《Go语言实战》里的一个简单例子来做多态的介绍

package main

import (
    "fmt"
)

// 定义一个通知类行为的接口
type notifier interface {
    notify()
}

// 定义一个用户类型
type user struct {
    name  string
    email string
}

// 用户类型使用指针接收者来实现接口
func (u *user) notify() {
    fmt.Printf("Sending user email to %s<%s>\n",
        u.name,
        u.email)
}

// 定义一个管理员类型
type admin struct {
    name  string
    email string
}

// 管理员类型使用指针接收者来实现接口
func (a *admin) notify() {
    fmt.Printf("Sending admin email to %s<%s>\n",
        a.name,
        a.email)
}

func main() {
    bill := user{"Bill", "bill@email.com"}
    sendNotification(&bill)

    lisa := admin{"Lisa", "lisa@email.com"}
    sendNotification(&lisa)
}

func sendNotification(n notifier) {
    n.notify()
}

上述代码中,sendNotification可以看成一个多态函数,这个函数接收一个实现了notifier接口的值作为参数。既然任何一个实体类型都能实现该接口,那么这个函数可以针对任意实体类型的值来实现其自身定义的独特的notifier方法,因此这个函数可以提供多态的行为。

输出如下:

Sending user email to Bill<bill@email.com>
Sending admin email to Lisa<lisa@email.com>

类型断言

value,ok := x.(T)

x表示一个接口,T表示一个类型(也可为接口类型)

该断言表达式会返回x的值和一个布尔值 可根据该布尔值判断x接口的实际类型是否为T类型

如:

func main() {
    var x interface{}
    x = 10
    value, ok := x.(int)
    fmt.Print(value, ",", ok)
}

输出

10,true

类型判断

用法和类型断言差不多,只是类型T改为关键词type

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("String: %s\n", i.(string))
    case int:
        fmt.Printf("Int: %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
func main() {  
    findType("Naveen")
    findType(77)
    findType(89.98)
}

还可以将类型与接口进行比较。如果我们有一个类型并且该类型实现了一个接口,那么可以将它与它实现的接口进行比较。

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}

func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown type\n")
    }
}

func main() {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}

接口的嵌套

可以通过嵌入其他接口来创建新的接口

type Inter1 interface {
    play1()
}

type Inter2 interface {
    play2() int
}

type InterMix interface {
    Inter1
    Inter2
}

这样如果一个实体类型实现了play1和play2两种方法,那么他就实现了InterMix这个接口,当然两个子接口也实现了

 go语言学习笔记(四)——继承
go语言学习笔记(二)——内建容器篇 
上一篇:go语言学习笔记(四)——继承
下一篇:go语言学习笔记(二)——内建容器篇


如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ