Go【No-12】接口

12-接口

Go 中的接口 interface 是一种类型,一种抽象的类型。

interface 是一组方法的集合,是 dack-type programming 的一种体现。
接口做的事情就像是定义一个协议(规则),只要一台机器具有洗衣服和甩干的功能,我就称其为洗衣机。

接口不关心属性(数据),只关心行为(方法)。

只要一个结构体 X 实现了接口 A 中所有的方法,就称这个结构体 X 为接口 A 的实现类,称结构体 X 实现了接口 A。还可称结构体 X 是 A 类型。

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

定义接口

基本语法如下:

type identifier interface {
    methodName1([param])([return])
    methodName2([param])([return])
    methodName3([param])([return])
    ...
}

identifier:接口名,命名时按照规范应该加上 er,如 有字符串功能的 Stringer、有读取功能的 Reader
methodName:方法名

eg:

type USBer interface {
    start()
    end()
}

实现接口

实现接口:

// 定义一个接口
type USBer interface {
    start()
    end()
}
// 定义一个结构体
type Mouse struct {
    name  string
}

// 实现两个接口中的方法,接收者为结构体 Mouse,这样 Mouse 就是一个实现类
func (m Mouse) start() {
    fmt.Println(m.name, "starts working.")
}

func (m Mouse) end() {
    fmt.Println(m.name, "end working.")
}

结构体 Mouse 实现了 USBer 的所有方法 start()end(),所以 Mouse 就是 USBer 的一个实现类。

使用接口

实现了接口的结构体(实现类)和没实现接口的结构体,区别在于
当一个函数需要传递一个接口类型的参数时,可以传递一个接口变量进去,也可以传一个实现类对象进去。

func test(usb USBer) {    // 需要传递一个接口类型的参数
    usb.start()
    usb.end()
}

func main() {
	m1 := Mouse{"罗技"}
	test(m1)    // 传递一个实现类对象

	var u1 USBer = m1
	test(u1)    // 传递一个接口变量
}

注意,
接口变量使用之前,需要先赋一个实现类对象。
接口变量可以调用接口中的方法,但不可以调用实现类的属性和方法

package main

import "fmt"

// 定义一个接口
type USBer interface {
    start()
    end()
}
// 定义一个结构体
type Mouse struct {
    name  string
}

// 实现两个接口中的方法,接收者为结构体 Mouse,这样 Mouse 就是一个实现类
func (m Mouse) start() {
    fmt.Println(m.name, "starts working.")
}

func (m Mouse) end() {
    fmt.Println(m.name, "end working.")
}

// 定义结构体 Mouse 自己的方法
func (m Mouse) selfMethod() {
    fmt.Println(m.name, "selfMethod")
}

// 定义一个测试方法,需要接收一个 USB 接口变量。可以传递 USB 变量,也可以传递 USB 实现类的对象
func test(usb USBer) {    // 需要传递一个接口类型的参数
    usb.start()
    usb.end()
}

func main() {
	m1 := Mouse{"罗技"}
	// Mouse是一个实现类,可以传递实现类对象给 test()
	test(m1)

    // 接口变量使用之前要先赋一个实现类对象
	var u1 USBer = m1
	// 传递一个接口变量给 test()
	test(u1)

    m1.start()           // 罗技 starts working.
    m1.end()             // 罗技 end working.
    m1.selfMethod()      // 罗技 selfMethod

    u1.start()           // 罗技 starts working.
    u1.end()             // 罗技 end working.

    // u1.selfMethod()   // 错误,接口变量不可以调用实现类的方法
    // u1.name           // 错误,接口变量不可以调用实现类的属性
}

空接口

不包含任何方法的接口,称为空接口。
我们可以认为 任意类型都实现了空接口

interface {}

type T interface {}

eg:

type T interface{}

type person struct {
    name string
}

var t1 T = person{"Boii"}
var t2 T = "str"
var t3 T = 100
var t4 T = []int{1, 2, 3}

fmt.Println(t1)    // {Boii}
fmt.Println(t2)    // str
fmt.Println(t3)    // 100
fmt.Println(t4)    // [1 2 3]

空接口的作用在于 任意类型
当我们想要接受一个任意类型的参数,或者定义一个接收任意类型的容器,都可以使用空接口来代替。

func test (a interface{}) {
    ...
}

test("string")
test(123)
test([]int{1, 2, 3, 4})
a := []interface{}{1, 3, "str", true}
fmt.Println(a)    // {1 3 str true}

type T interface{}
b := []T{1, 3, "str", true}
fmt.Println(b)    // {1 3 str true}

接口嵌套

接口中不仅可以定义方法签名,还可以定义其他的接口。这种方式我们称为接口嵌套

// 接口A
type A interface {
    method1()
}

// 接口B
type B interface {
    method2()
}

// 接口C,嵌套了接口 A 和 B
type C interface {
    A
    B
    method3()
}

// 结构体
type person struct {
    name string
}

func (p person) method1() { // 使 person 成为 接口A 的实现类
    ...
}

func (p person) method2() { // 使 person 成为 接口B 的实现类
    ...
}

func (p person) method3() { // 使 person 成为 接口C 的实现类,前提是实现了前面两个
    ...
}

嵌套了接口,不仅要实现接口本身的方法,还要实现接口中的嵌套接口的方法,才能认为实现了最外侧的接口。

如上面例子,person 要成为 C 的实现类,不仅要实现 C 的 method3() 方法,还得实现 A 的 method1() 和 B 的method2()

func main() {
	p := person{"Boii"}
	p.method1()
	p.method2()
	p.method3()

	var a1 A = p
	a1.method1()
	// a1.method2() // !报错
	// a1.method3() // !报错

	var b1 B = p
	b1.method2()
	// b1.method1() // !报错
	// b1.method3() // !报错

	var c1 C = p
	c1.method1() // 正确
	c1.method2() // 正确
	c1.method3() // 正确

	var a2 A = c1   // 接口C变量看成是接口A类型,这是可以的
	a2.method1()    // 但是只能调用接口A的 method1()
	// a2.method2() // !报错
	// a2.method3() // !报错

	var c2 C = a1   // !报错,接口A变量看成是接口C类型,这是错误的
}

接口断言

设 定义了一个接口A,还有两个结构体 X、Y,两个结构体都实现了接口A,当一个函数或方法想要接收 X 或 Y 两种类型时,可以将形参设置为 接口A 类型,这样传递的时候不管是 X 的对象还是 Y 的对象都可以传递进来。

【Q】:那如果在方法或函数里,我需要确定到底是 X 还是 Y 怎么办?

type A interface {
    aMethod()
}

type X struct {
    name string
    age  int
}

type Y struct {
    name string
    sex  string
}

// 实现接口A
func (x X) aMethod() {
    fmt.Println("I am ", x)
}

func (y Y) aMethod() {
    fmt.Println("I am", y)
}

// 定义一个接收 X 和 Y 对象的函数
func test(a A) {
    a.aMethod()
}

【A】:这时候我们需要用到接口 类型断言 来确定传进来的到底是 x 还是 y。

  • 方式1:
    • instance := 接口对象.(实际类型),这种不安全,会引发 panic()
    • instance, ok := 接口对象.(实际类型),这种就安全。接口对象是实际类型时,ok 为 true
  • 方式2:switch
    switch instance := 接口对象.(type) {
    case 实际类型1: ...
    case 实际类型2: ...
    ...
    }
    

于是在 test() 函数中我们可以这样写:

// 定义一个接收 X 和 Y 对象的函数
func test (a A) {
	if ins, ok := a.(X); ok {	// 使用 if 断言
		fmt.Println(ins.age)
	} else if ins, ok := a.(Y); ok {
		fmt.Println(ins.sex)
	}
}

或者这样写:

// 定义一个接收 X 和 Y 对象的函数
func test(a A) {
	switch ins := a.(type) {	// 使用 switch 判断
	case X:
		fmt.Println(ins.age)
	case Y:
		fmt.Println(ins.sex)
	}
}

值类型实现 VS 指针类型实现

我们先定义一个接口,和两个结构体

然后分别使用 值类型 和 指针类型 实现接口

type Aer interface {
    method()
}
type X struct {}
type Y struct {}

// 值类型实现
func (x X) method() {}

// 指针类型实现
func (y *Y) method() {}

此时实现接口的是 X 类型 和 *Y 类型。

接着我们来使用一下:

func main() {
	x := X{}
	y := Y{}

	var a1 Aer = x    // a1 可以接收 x 类型
	var a2 Aer = &x   // a2 可以接收 *X 类型

	var a3 Aer = y    // !报错,a3 不可以接收 y 类型
	var a4 Aer = &y   // a4 可以接收 *y 类型
}

在使用值类型实现接口之后,不管是 X 结构体类型 还是 *X 结构体指针类型,都可以赋值给该接口变量。

因为在 Go 语言中有对指针类型变量求值的语法糖,X 指针 &x 内部会自动求值。
var a2 Aer = &x 会变成 var a2 Aer = *(&x)

而 Y 是用指针类型实现的,所以只能传递指针 &y 给 Aer 接口变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TCP404

老板大方~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值