go的接口有何用处? | 青训营

92 阅读4分钟

当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以-被称为鸭子。 --詹姆斯·惠特克姆·莱利

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{}