Go struct 和 interface:结构体与接口都实现了哪些功能?

接口


接口定义一组方法集合
type IF interface {
Method1(param_list) return_type
}
适用场景:Kubernetes 中有大量的接口抽象和多种实现(比如定义了一堆标准接口,然后通过各个厂商来适配,每个厂商都会有自己的实现方法,框架代码的话,只需要去定义接口里面定义的这些method,至于这些method到底是谁执行就看你每个接口的实现了)
Struct 无需显示声明实现 interface,只需直接实现方法
Struct 除实现 interface 定义的接口外,还可以有额外的方法
一个类型可实现多个接口(Go 语言的多重继承)
Go 语言中接口不接受属性定义
接口可以嵌套其他接口

定义一个统一的接口,然后用多个结构体去实现这些接口,这些结构体其实是可以加到同一个接口切片里面的,在打印的时候,也就是在调用函数的时候,go语言能够自动的判断它到底是哪一个具体的类型,然后调用其具体的实现。

注意事项


Interface 是可能为 nil 的,所以针对 interface 的使用一定要预先判空,否则会引起程序 crash(nil panic)(interface要先赋值再去访问,那么会出现上面说的情况)
Struct 初始化意味着空间分配,对 struct 的引用不会出现空指针

接口


接口的定义

接口是和调用方的一种约定,它是一个高度抽象的类型,不用和具体的实现细节绑定在一起接口要做的是定义好约定,告诉调用方自己可以做什么,但不用知道它的内部实现,这和我们见到的具体的类型如 int、map、slice 等不一样。

接口的定义和结构体稍微有些差别,虽然都以 type 关键字开始,但接口的关键字是 interface,表示自定义的类型是一个接口。也就是说 Stringer 是一个接口,它有一个方法 String() string,整体如下面的代码所示:

type Stringer interface {

    String() string

}

提示:Stringer 是 Go SDK 的一个接口,属于 fmt 包。

针对 Stringer 接口来说,它会告诉调用者可以通过它的 String() 方法获取一个字符串,这就是接口的约定。至于这个字符串怎么获得的,长什么样,接口不关心,调用者也不用关心,因为这些是由接口实现者来做的。

接口的实现
接口的实现者必须是一个具体的类型,继续以 person 结构体为例,让它来实现 Stringer 接口,如下代码所示: 

func (p person) String()  string{

    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)

}

给结构体类型 person 定义一个方法,这个方法和接口里方法的签名(名称、参数和返回值)一样,这样结构体 person 就实现了 Stringer 接口。 

 注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。

实现了 Stringer 接口后就可以使用了。首先我先来定义一个可以打印 Stringer 接口的函数,如下所示:

func printString(s fmt.Stringer){

    fmt.Println(s.String())

}

这个被定义的函数 printString,它接收一个 Stringer 接口类型的参数,然后打印出 Stringer 接口的 String 方法返回的字符串。

printString 这个函数的优势就在于它是面向接口编程的,只要一个类型实现了 Stringer 接口,都可以打印出对应的字符串,而不用管具体的类型实现。

因为 person 实现了 Stringer 接口,所以变量 p 可以作为函数 printString 的参数,可以用如下方式打印:

printString(p)

 结果为:

the name is 飞雪无情,age is 30

现在让结构体 address 也实现 Stringer 接口,如下面的代码所示:

func (addr address) String()  string{

    return fmt.Sprintf("the addr is %s%s",addr.province,addr.city)

}

因为结构体 address 也实现了 Stringer 接口,所以 printString 函数不用做任何改变,可以直接被使用,打印出地址,如下所示:

printString(addr)

//输出:the addr is 北京北京

这就是面向接口的好处,只要定义和调用双方满足约定,就可以使用,而不用管具体实现。接口的实现者也可以更好的升级重构,而不会有任何影响,因为接口约定没有变。

值接收者和指针接收者
我们已经知道,如果要实现一个接口,必须实现这个接口提供的所有方法,我们也知道定义一个方法,有值类型接收者和指针类型接收者两种。二者都可以调用方法,因为 Go 语言编译器自动做了转换,所以值类型接收者和指针类型接收者是等价的。

但是在接口的实现中,值类型接收者和指针类型接收者不一样,下面我会详细分析二者的区别。

在上一小节中,已经验证了结构体类型实现了 Stringer 接口,那么结构体对应的指针是否也实现了该接口呢?我通过下面这个代码进行测试:

printString(&p)

测试后会发现,把变量 p 的指针作为实参传给 printString 函数也是可以的,编译运行都正常。这就证明了以值类型接收者实现接口的时候,不管是类型本身,还是该类型的指针类型,都实现了该接口

示例中值接收者(p person)实现了 Stringer 接口,那么类型 person 和它的指针类型*person就都实现了 Stringer 接口。

现在,我把接收者改成指针类型,如下代码所示:

func (p *person) String()  string{

    return fmt.Sprintf("the name is %s,age is %d",p.name,p.age)

}

 修改成指针类型接收者后会发现,示例中这行 printString(p) 代码编译不通过,提示如下错误:

./main.go:17:13: cannot use p (type person) as type fmt.Stringer in argument to printString:

    person does not implement fmt.Stringer (String method has pointer receiver)

意思就是类型 person 没有实现 Stringer 接口。这就证明了以指针类型接收者实现接口的时候,只有对应的指针类型才被认为实现了该接口。

我用如下表格为你总结这两种接收者类型的接口实现规则:

 可以这样解读:

  • 当值类型作为接收者时,person 类型和*person类型都实现了该接口。
  • 当指针类型作为接收者时,只有*person类型实现了该接口。

可以发现,实现接口的类型都有*person,这也表明指针类型比较万能,不管哪一种接收者,它都能实现该接口。

---------------------------------------------------------------------

接口定义使用 interface 标识,声明了一系列的函数签名 (函数名、函数参数、函数返回值),
在定义接口时可以 指定接口名称,在后续声明接口变量时使用。

声明接口变量只需要定义变量类型为接口名,此时变量被初始化为 nil

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/123668076