Go语言基础 – 接口

  • A+
所属分类:go语言编程 学编程

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

接口

接口类型

在Go语言中接口(interface)是一种类型,一种抽象的类型。

interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。

接口定义

Go语言提倡面向接口编程。

每个接口由数个方法组成,接口的定义格式如下:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}

其中:

1.接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。

2.方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。

3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

实现接口的条件

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

我们来定义一个Sayer1接口:

package main

import (
    "fmt"
)

type dog struct {      // 定义dog结构体
}
type cat struct {      // 定义cat结构体
}

func (self dog) say() {       // 定义dog的say方法
    fmt.Println("dog say: 汪汪汪")
}
func (self cat) say() {       // 定义cat的say方法
    fmt.Println("cat say: 喵喵喵")
}

type sayer1 interface {      // 定义sayer1接口类型,里面只有say方法
    say()
}

func test(arg sayer1) {      // 这个函数接收一个变量,变量的类型是sayer1类型, (所有实现了sayer1接口里的方法的类型也可以称为sayer1类型)  
    arg.say()       // 执行参数的say()方法
}

func main() {
    var x sayer1      // 初始化sayer1接口
    c1 := cat{}       // 初始化cat结构体 
    test(c1)          // 执行test函数,将cat结构体初始化后的值作为参数传给函数,结果为:cat say: 喵喵喵
    d1 := dog{}       // 初始化dog结构体
    test(d1)          // 执行test函数,将dog结构体初始化后的值作为参数传给函数,结果为:dog say: 汪汪汪

    x = c1            // 将cat结构体初始化后的值赋值给sayer1接口初始化后的变量,结果为:cat say: 喵喵喵
    x.say()           // 通过接口来执行cat的say()方法
}

接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dog和cat类型的变量。

方法的参数为值类型接收者和指针接收者的区别与接口嵌套示例

方法为值类型接收者和指针接收者的区别
1.当方法的参数为值类型接收者时:使用值类型和指针类型调用都能正常调用方法

2.当方法的参数为指针类型接收者时:使用指针类型能正常调用方法,值类型调用会报错

3.接口嵌套:一个接口里包含多个接口,就称为接口嵌套

类型和接口的关系
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。我们就分别定义Sayer接口和Mover接口,如下: Mover接口。

package main

import (
    "fmt"
)

// 接口使用值接收者和指针类型接收者的区别

// 接口嵌套,当前接口里嵌套了其他接口
type animal interface{      // animal接口,该接口嵌套了mover接口和sayer接口
    mover
    sayer
}

type mover interface {       // mover接口
    move()
}
type sayer interface {       // sayer接口
    say()
}

type person struct {        // 定义person结构体
    Name string
    Age int
}

// 方法使用值类型接收者时 - 使用值类型和指针类型调用都能保存到接口变量中
func (self person) move() {
    fmt.Printf("%s 在奔跑...\n",self.Name)
    fmt.Printf("data: %v\n",self)
}
func (self person) say() {
    fmt.Printf("%s 在咆哮...\n",self.Name)
    fmt.Printf("data: %v\n",self)
}

// 方法使用指针类型接收者时 - 使用指针类型调用能保存到接口变量中,值类型会报错
// func (self *person) move() {
//  fmt.Printf("%s 在奔跑...\n",self.Name)
//  fmt.Printf("data: %v\n",self)
// }

func main() {
    var m mover
    var s sayer
    var a animal
    p1 := person{
        Name: "追梦人",
        Age: 18,
    }
    // p2 := &person {
    //  Name: "老弟",
    //  Age: 20,
    // }

    // 类型满足多个接口的方法可以调用多个接口
    m = p1
    s = p1
    m.move()
    s.say()

    // m = p2
    // s = p2
    // m.move()
    // s.say()

    // 接口嵌套
    a = p1
    a.move()
    a.say()
}

多个类型实现同一接口

Go语言中不同的类型还可以实现同一接口 首先我们定义一个Mover接口,它要求必须由一个move方法。

例如狗可以动,汽车也可以动,可以使用如下代码实现这个关系:

type Mover interface {
    move()
}

type dog struct {
    name string
}

type car struct {
    brand string
}

// dog类型实现Mover接口
func (d dog) move() {
    fmt.Printf("%s会跑\n", d.name)
}

// car类型实现Mover接口
func (c car) move() {
    fmt.Printf("%s速度70迈\n", c.brand)
}

这个时候我们在代码中就可以把狗和汽车当成一个会动的物体来处理了,不再需要关注它们具体是什么,只需要调用它们的move方法他们就可以动了。

func main() {
    var x Mover
    var a = dog{name: "旺财"}
    var b = car{brand: "保时捷"}
    x = a
    x.move()        // 结果:旺财会跑
    x = b
    x.move()       // 保时捷速度70迈
}

并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。

// WashingMachine 洗衣机
type WashingMachine interface {
    wash()
    dry()
}

// 甩干器
type dryer struct{}

// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
    fmt.Println("甩一甩")
}

// 海尔洗衣机
type haier struct {
    dryer //嵌入甩干器
}

// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
    fmt.Println("洗刷刷")
}

空接口

空接口没有方法 - 也就是所有类型的变量都实现了空接口,即所有类型的变量都可以给空接口赋值

空接口的用处:
1.空接口可以用作函数的参数

2.空接口可以用作map的value

空接口的定义与使用

// 1.空接口的定义
type xxx interface {        // 接口中没定义任何方法,该接口就是一个空接口
}

// 2.空接口可以直接使用
var m map[string]interface{}
m = make(map[string]interface{},3)
m["name"] = "Dream"
m["age"] = 18
m["score"] = 98.9
fmt.Println(m)         // 结果:map[age:18 name:Dream score:98.9]

类型断言

空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
这里就用到了类型断言,具体示例如下:

func typeDemo(t interface{}) {          // 空接口可以不用先定义直接用
    // 单个类型断言
    // ret, ok := t.(string)     // 判断x的类型是不是string类型,是:ret是结果,ok为:true,不是:ret为:空串,ok为:false
    // if !ok {
    //  fmt.Println("type ERROR")
    // }else {
    //  fmt.Println("type OK",ret)
    // }

    // 使用switch进行多类型断言
    switch v := t.(type) {
        case string:
            fmt.Printf("type is a string,value is %v\n", v)
        case int:
            fmt.Printf("type is a int, value is %v\n", v)
        case bool:
            fmt.Printf("type is a bool, value is %v\n", v)
        default:
            fmt.Println("unsupport type!")
    }
}

func endfunc1() {
    var age int  = 18          // 定义int类型变量
    var str string = "test string"        // 定义字符串类型变量
    var bo bool = true         // 定义bool类型变量
    var m map[string]interface{}         // 定义map类型变量
    m = make(map[string]interface{},3)
    m["name"] = "张三"
    m["age"] = 18
    m["score"] = 99.8
    typeDemo(age)          // 调用typeDemo函数并将多个类型的变量当参数传给函数,让函数做类型断言
    typeDemo(str)
    typeDemo(bo)
    typeDemo(m)
}

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: