2、接口

接口

Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

Go 的接口有以下特性:

定义接口

接口定义规则如下:

在Go中,接口是一种类型,它指定了一个方法集:

// 定义一个接口
type Reader interface {
    Read(p []byte) (n int, err error)
}

// 另一个包含多个方法的接口
type Shape interface {
    Area() float64
    Perimeter() float64
}

实现接口

实现接口的条件如下:

Go接口的特别之处在于它们是隐式实现的,即如果一个类型实现了接口中定义的所有方法,它就自动满足了该接口,无需显式声明:

// Circle类型
type Circle struct {
    Radius float64
}

// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// 实现Shape接口的Perimeter方法
func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// Rectangle类型
type Rectangle struct {
    Width, Height float64
}

// 实现Shape接口的方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// 使用接口作为参数
func PrintShapeInfo(s Shape) {
    fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 3, Height: 4}
    
    // 都可以作为Shape传递给函数
    PrintShapeInfo(c)
    PrintShapeInfo(r)
}

其他类型实现

**注意:**在 Go 中,不是只有 struct 类型可以实现接口!其他的所有类型,只要实现了接口的所有方法,就自动实现了该接口。

package main

type MyInterface interface {
	MyTestFunc(int) int
}

// MyFunc 类型实现了 MyInterface 接口
type MyFunc func(string) string

func (m MyFunc) MyTestFunc(a int) int {
	return a
}

// MyInt 类型实现了 MyInterface 接口
type MyInt int

func (i MyInt) MyTestFunc(a int) int {
	return a
}

接口嵌套

接口可以嵌套其他接口,形成更大的接口:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter接口嵌套了Reader和Writer
type ReadWriter interface {
    Reader
    Writer
}

实现ReadWriter接口的类型必须同时实现ReadWrite方法。

一个类型可以实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

网络上的两个程序通过一个双向的通信连接实现数据的交换,连接的一端称为一个 Socket。Socket 能够同时读取和写入数据,这个特性与文件类似。因此,开发中把文件和 Socket 都具备的读写特性抽象为独立的读写器概念。

Socket 和文件一样,在使用完毕后,也需要对资源进行释放。

把 Socket 能够写入数据和需要关闭的特性使用接口来描述:

type Socket struct {
    
}
func (s *Socket) Write(p []byte) (n int, err error) {    
    return 0, nil
}
func (s *Socket) Close() error {    
    return nil
}

Socket 结构的 Write() 方法实现了 io.Writer 接口:

type Writer interface {  
    Write(p []byte) (n int, err error)
}

同时,Socket 结构也实现了 io.Closer 接口:

type Closer interface {    
    Close() error
}

使用 Socket 实现的 Writer 接口的代码,无须了解 Writer 接口的实现者是否具备 Closer 接口的特性。同样,使用 Closer 接口的代码也并不知道 Socket 已经实现了 Writer 接口,如下图所示:

img

多个类型可以实现相同的接口

例如,现在需要有一个接口,这个接口可以实现服务的开启和日志功能,而日志功能是多个服务通用的,只有开启功能需要根据服务的不同来定义,每个服务都有这两个功能,那么就可以这么去做

package main

import "fmt"

//Service 服务接口
type Service interface {
	start()
	log()
}

// Logger 通用的日志处理器
type Logger struct{}

func (log *Logger) log() {
	fmt.Println("通用日志服务")
}

// GameService 游戏服务
type GameService struct {
	//内嵌日志处理器
	Logger
}

//实现游戏服务的启动功能
func (gs *GameService) start() {
	fmt.Printf("开启游戏服务")
}

func main() {
	var service Service = new(GameService)
	service.log()
	service.start()
}

此时,如果再新增服务的话,只需要内嵌日志处理器然后再实现服务特有的启动功能,就可以了

接口和nil

nil 在 Go语言中只能被赋值给指针和接口。接口在底层的实现有两个部分:typedata

在源码中,显式地将 nil 赋值给接口时,接口的 typedata 都将为 nil。此时,接口与 nil 值判断是相等的。

但如果将一个带有类型的 nil 赋值给接口时,只有 datanil,而 typenil,此时,接口与 nil 判断将不相等。

空接口

interface{}
any
//在1.18后,可以使用any来表示空接口,源码中是对空接口做了别名
type any = interface{}

空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。从实现的角度看,任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从空接口中取出原值。

空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。

空接口比较

Go 语言中的空接口在保存不同的值后,可以和其他变量一样使用==进行比较操作。

类 型 说 明
map 不可比较,如果比较,程序会报错
切片(slice) 不可比较,如果比较,程序会报错
通道(channel) 可比较,必须由同一个 make 生成,也就是同一个通道才会是true,否则为 false
数组 可比较,编译期知道两个数组是否一致
结构体 可比较,可以逐个比较结构体的值
函数 可比较

类型断言

类型断言(Type Assertion)是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型,也可以作为接口和具体类型直接转换的方法,语法格式如下:

value, ok := x.(T)

x 表示一个接口的类型变量,T 表示一个具体的类型(也可为接口类型)。

该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型

注意:需要注意如果不接收第二个参数也就是上面代码中的 ok,断言失败时会直接造成一个 panic,也就是说,如果断言成功,就可以只接一个值,就是value

package main

import "fmt"

func main() {
	var a interface{} = 10
	var b interface{} = "hello"
	v1, o1 := a.(int)
	v2, o2 := b.(int)
	fmt.Println(v1, o1) //10 true
	fmt.Println(v2, o2) //0 false
}

类型分支

type-switch 流程控制的语法或许是Go语言中最古怪的语法。 它可以被看作是类型断言的增强版。它和 switch-case 流程控制代码块有些相似。

switch 接口变量.(type) {
    case 类型1:
        // 变量是类型1时的处理
    case 类型2:
        // 变量是类型2时的处理
    …
    default:
        // 变量不是所有case中列举的类型时的处理
}
package main

import "fmt"

func main() {
	var a interface{} = "hello"
	switch a.(type) {
	case int:
		fmt.Printf("a is int type,value = %s", a)
	case string:
		fmt.Printf("a is string type,value = %s", a)
	case float64:
		fmt.Printf("a is float64 type,value = %s", a)
	}

	//a is string type,value = hello
}

接口设计模式

io包中的接口设计

Go标准库中的io包是接口设计的典范,这种设计允许:

// io包核心接口
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// 组合接口
type ReadWriter interface {
    Reader
    Writer
}

type ReadCloser interface {
    Reader
    Closer
}

// 更多组合...

通过结构体组合可以轻松实现多个接口:

// 实现Reader接口
type StringReader struct {
    data string
    pos  int
}

func (r *StringReader) Read(p []byte) (n int, err error) {
    // 实现细节...
    return len(p), nil
}

// 实现Writer接口
type StringWriter struct {
    data string
}

func (w *StringWriter) Write(p []byte) (n int, err error) {
    // 实现细节...
    return len(p), nil
}

// 通过组合实现ReadWriter接口
type StringReadWriter struct {
    *StringReader
    *StringWriter
}

// 可以使用工厂函数创建组合结构
func NewStringReadWriter(data string) *StringReadWriter {
    return &StringReadWriter{
        StringReader: &StringReader{data: data},
        StringWriter: &StringWriter{},
    }
}

错误处理中的接口应用

// 标准错误接口
type error interface {
    Error() string
}

// 自定义错误类型
type NetworkError struct {
    Code    int
    Message string
}

func (e NetworkError) Error() string {
    return fmt.Sprintf("网络错误 [%d]: %s", e.Code, e.Message)
}

// 错误断言
func handleError(err error) {
    if netErr, ok := err.(NetworkError); ok {
        fmt.Printf("处理网络错误,代码: %d\n", netErr.Code)
    } else {
        fmt.Println("处理其他错误")
    }
}

插件系统设计

// 插件接口
type Plugin interface {
    Name() string
    Initialize() error
    Execute(ctx context.Context) error
    Shutdown() error
}

// 插件管理器
type PluginManager struct {
    plugins map[string]Plugin
}

func NewPluginManager() *PluginManager {
    return &PluginManager{
        plugins: make(map[string]Plugin),
    }
}

func (pm *PluginManager) Register(p Plugin) error {
    name := p.Name()
    if _, exists := pm.plugins[name]; exists {
        return fmt.Errorf("插件 %s 已注册", name)
    }
    
    if err := p.Initialize(); err != nil {
        return fmt.Errorf("插件 %s 初始化失败: %v", name, err)
    }
    
    pm.plugins[name] = p
    return nil
}

func (pm *PluginManager) ExecuteAll(ctx context.Context) error {
    for name, p := range pm.plugins {
        if err := p.Execute(ctx); err != nil {
            return fmt.Errorf("插件 %s 执行失败: %v", name, err)
        }
    }
    return nil
}

func (pm *PluginManager) ShutdownAll() {
    for name, p := range pm.plugins {
        if err := p.Shutdown(); err != nil {
            fmt.Printf("插件 %s 关闭失败: %v\n", name, err)
        }
    }
}

// 示例插件
type LoggingPlugin struct{}

func (p *LoggingPlugin) Name() string {
    return "logging"
}

func (p *LoggingPlugin) Initialize() error {
    fmt.Println("日志插件初始化")
    return nil
}

func (p *LoggingPlugin) Execute(ctx context.Context) error {
    fmt.Println("日志插件执行")
    return nil
}

func (p *LoggingPlugin) Shutdown() error {
    fmt.Println("日志插件关闭")
    return nil
}