Go语言深入理解—接口|青训营

34 阅读4分钟

Go语言深入理解—接口|青训营

简介

最近在阅读其他Go语言开源项目时经常可以接触到接口这个概念,因此阅读通过 《The Go Programming Language》等书籍深入地学习Go语言的接口。

1.接口是什么

在 Go 语言中,接口(Interface)是一种抽象类型,用于定义对象的行为规范。接口描述了一组方法的签名,任何实现了这些方法的类型都被认为是实现了该接口。简而言之,接口就是一种约束形式,其中只包括成员函数定义,不包含成员函数实现,一个对象通过实现不同的接口,接口类型不会和特定的实现细节绑定在一起,可以灵活地完成很多任务。

Go语言中接口类型的独特之处在于它是满足隐式实现的,只要一个类型的方法匹配接口的方法签名,它就被视为实现了该接口,不需要显式声明。这种特性使得 Go 语言中的接口使用更加灵活。

2.从fmt.Sprintffmt.Printf看接口

fmt.Printf会把结果写到标准输出,

fmt.Sprintfr会把结果以字符串的形式返回。

package fmt

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) {
    return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...interface{}) string {
    var buf bytes.Buffer
    Fprintf(&buf, format, args...)
    return buf.String()
}

这两个函数都使用了另一个函数fmt.Fprintf来进行封装。

在这里,着重关注Fprintf函数的第一个参数io.Writer,这个参数并不是一个文件类型。在fmt.Sprintffmt.Sprintf函数中,调用的虽然都是Fprintf函数,但是他们的参数io.Writer明显不是一个类型。在Printf函数中的第一个参数os.Stdout*os.File类型;在Sprintf函数中的第一个参数&buf是一个指向可以写入字节的内存缓冲区,并不是一个文件类型。

io.Writer类型的定义如下:

package io

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    // Write writes len(p) bytes from p to the underlying data stream.
    // It returns the number of bytes written from p (0 <= n <= len(p))
    // and any error encountered that caused the write to stop early.
    // Write must return a non-nil error if it returns n < len(p).
    // Write must not modify the slice data, even temporarily.
    //
    // Implementations must not retain p.
    Write(p []byte) (n int, err error)
}

io.Writer 类型为函数 Fprintf 和其调用者之间定义了一种协议。一方面,这个协议要求调用者提供具体类型的值,例如 *os.File*bytes.Buffer,这些值必须拥有特定的 Write 函数签名和行为。另一方面,这个协议确保了 Fprintf 可以操作满足 io.Writer 接口的任何值。

io.Writer类型是用得最广泛的接口之一,因为它提供了所有类型的写入bytes的抽象。

3.如何定义接口与实现

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

在 Go 语言中,定义一个接口是通过使用 type 关键字和 interface{} 语法来完成的。接口定义了一组方法的签名,任何实现了这些方法的类型都被视为实现了该接口。以下是定义一个接口的一般步骤:

goCopy codepackage main

import "fmt"

// 定义一个接口
type MyInterface interface {
    Method1() int
    Method2() string
}

// 定义一个类型,实现了 MyInterface 接口
type MyType struct {
    Data1 int
    Data2 string
}

// 实现 MyInterface 接口的方法
func (mt MyType) Method1() int {
    return mt.Data1//返回类型必须与接口约束的一致
}

func (mt MyType) Method2() string {
    return mt.Data2
}

func main() {
    // 创建一个 MyType 类型的实例
    myInstance := MyType{Data1: 42, Data2: "Hello"}

    // 将实例赋值给接口变量
    var myInterfaceVar MyInterface
    myInterfaceVar = myInstance

    // 调用接口方法
    fmt.Println("Method1:", myInterfaceVar.Method1())
    fmt.Println("Method2:", myInterfaceVar.Method2())
}

在上面的示例中,首先定义了一个名为 MyInterface 的接口,它包含了两个方法 Method1()Method2() 的签名。然后,我们定义了一个名为 MyType 的结构体类型,并为它实现了 MyInterface 接口的方法。最后,我们在 main() 函数中创建了一个 MyType 实例,并将该实例赋值给一个类型为 MyInterface 的接口变量 myInterfaceVar,然后调用了接口方法。

简单来说,一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。