Go语言深入理解—接口|青训营
简介
最近在阅读其他Go语言开源项目时经常可以接触到接口这个概念,因此阅读通过 《The Go Programming Language》等书籍深入地学习Go语言的接口。
1.接口是什么
在 Go 语言中,接口(Interface)是一种抽象类型,用于定义对象的行为规范。接口描述了一组方法的签名,任何实现了这些方法的类型都被认为是实现了该接口。简而言之,接口就是一种约束形式,其中只包括成员函数定义,不包含成员函数实现,一个对象通过实现不同的接口,接口类型不会和特定的实现细节绑定在一起,可以灵活地完成很多任务。
Go语言中接口类型的独特之处在于它是满足隐式实现的,只要一个类型的方法匹配接口的方法签名,它就被视为实现了该接口,不需要显式声明。这种特性使得 Go 语言中的接口使用更加灵活。
2.从fmt.Sprintf
与fmt.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.Sprintf
与fmt.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
,然后调用了接口方法。
简单来说,一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。