当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以-被称为鸭子。 --詹姆斯·惠特克姆·莱利
Golang的interface
在 Go 语言中,interface 是一种类型,用于定义一组方法的集合,而不关心具体的实现。它是一种契约,规定了一个类型应该具有的方法签名,而不关心该类型的具体结构。通过定义接口,你可以在代码中实现多态性,允许不同的类型实现相同的接口,并且可以通过接口来实现对这些不同类型的统一处理。
接口实现示例
示例代码
// 定义一个形状接口
type Shape interface {
Area() float64
}
// 定义一个结构体类型 Circle,并实现 Shape 接口的方法
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
// 定义一个结构体类型 Rectangle,并实现 Shape 接口的方法
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func OutArea(shape Shape) {
//不关心该类型的具体结构
fmt.Printf("面积为%f\n", shape.Area())
}
func main() {
// var shape Shape = Circle{Radius: 5}
// OutArea(shape)
// shape = Rectangle{Width: 4, Height: 3}
// OutArea(shape)
shapeList := []Shape{Circle{Radius: 5}, Rectangle{Width: 4, Height: 3}}
for _, v := range shapeList {
OutArea(v)
}
}
这里我们就用go的接口实现了多态,我们先声明了一个接口类型的值,只要实现了这个接口的struct变量,都可以赋值给它,而调用方法时,go会根据实际类型选择使用哪个struct的方法。
接口嵌入
我们知道go的struct可以通过嵌入实现代码复用,go的接口也支持嵌入,来看一个go标准库的例子。go标准库里边定义了Reader和Writer接口如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
只要一个结构体实现了Read或者Write方法,它就分别实现了Read和Writer接口,比如我们可以嵌套这俩接口,声明一个新接口ReadWriter
// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
Reader
Writer
}
示例代码
package main
import (
"fmt"
)
type Graphics interface {
Shape
Size
}
// 定义一个形状接口
type Shape interface {
Area() float64
}
type Size interface {
Circumference() float64
}
// 定义一个结构体类型 Circle,并实现 Graphics 接口的方法
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Circumference() float64 {
return 2 * 3.14159 * c.Radius
}
// 定义一个结构体类型 Rectangle,并实现 Graphics 接口的方法
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Circumference() float64 {
return 2 * (r.Height + r.Width)
}
func OutArea(gra Graphics) {
//不关心该类型的具体结构
fmt.Printf("面积为%f,周长为%f\n", gra.Area(), gra.Circumference())
}
func main() {
shapeList := []Graphics{Circle{Radius: 5}, Rectangle{Width: 4, Height: 3}}
for _, v := range shapeList {
OutArea(v)
}
}
首先,定义了一个 Graphics 接口,这个接口嵌套了 Shape 和 Size 两个接口。这表示 Graphics 接口要求实现它的类型不仅要满足 Shape 接口的方法,还要满足 Size 接口的方法。
定义了 Shape 接口,其中有一个方法 Area() 用于计算图形的面积。
定义了 Size 接口,其中有一个方法 Circumference() 用于计算图形的周长。
定义了 Circle 结构体,实现了 Shape 和 Size 接口的方法。这里的 Circle 结构体有一个 Radius 字段,用于表示圆的半径。
定义了 Rectangle 结构体,同样实现了 Shape 和 Size 接口的方法。Rectangle 结构体有 Width 和 Height 字段,表示矩形的宽和高。
OutArea 函数接受一个实现了 Graphics 接口的参数,然后通过该接口调用其 Area() 和 Circumference() 方法,打印出面积和周长。
在 main 函数中,创建了一个包含两个不同形状的 Graphics 类型的切片 shapeList,其中包括一个 Circle 和一个 Rectangle。然后使用 for 循环遍历切片,对每个元素调用 OutArea 函数,输出其面积和周长。
这段代码展示了如何利用接口和多态性,以统一的方式处理不同类型的图形,而不关心具体类型的实现细节。这种方式使得你可以在代码中实现更灵活的结构,并且可以在不修改现有代码的情况下添加新的图形类型。
类型断言
上文我们看到在使用接口的地方,我们可以传入一个具体的实现了接口的struct类型,但是我们如何获取传入的到底是那种struct类型呢?go提供了一种方式交叫类型断言来获取具体的类型,他的语法比较简单,格式如下:
instance,ok := interfaceVal.(RealType)
我们继续上边的代码里加上类型断言的演示,注意类型断言那几行代码,再for循环里边我们使用类型断言获取了接口值的真正类型。
shapeList := []Graphics{Circle{Radius: 5}, Rectangle{Width: 4, Height: 3}}
for _, v := range shapeList {
if Circle, ok := v.(Circle); ok {
fmt.Println("我是圆形:", OutArea(Circle))
}
if Rectangle, ok := v.(Rectangle); ok {
fmt.Println("我是矩形:", OutArea(Rectangle))
}
}
使用空接口实现泛型
在go语言中,官方源码使用了interface{},也就是一个空接口定义any类型,也就是说,一个没有任何方法的接口,所有类型都实现了它。
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}