一、Go 接口定义
接口(interface)是 Go 语言中的一种类型,用于定义行为的集合,它通过描述类型必须实现的方法,规定了类型的行为契约。
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
Go 的接口设计简单却功能强大,是实现多态和解耦的重要工具。
接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
一言以蔽之:
Go 的接口 = 行为的集合,关注"你能做什么",而不是"你是谁"
最重要的特点:
-
隐式实现(无需
implements) -
面向行为,而不是类型层级
-
接口通常很小(1~2 个方法)
-
只要一个类型实现了接口要求的所有方法,该类型就自动被认为实现了该接口。
接口类型变量:
- 接口变量可以存储实现该接口的任意值。
- 接口变量实际上包含了两个部分:
- 动态类型:存储实际的值类型。
- 动态值:存储具体的值。
零值接口:
- 接口的零值是
nil。 - 一个未初始化的接口变量其值为
nil,且不包含任何动态类型或值。
空接口:
- 定义为
interface{},可以表示任何类型。
接口的常见用法
- 多态:不同类型实现同一接口,实现多态行为。
- 解耦:通过接口定义依赖关系,降低模块之间的耦合。
- 泛化 :使用空接口
interface{}表示任意类型。
二、接口的基本定义
Go
type Reader interface {
Read(p []byte) (n int, err error)
}
三、隐式实现
1.定义一个接口
Go
type Flyable interface {
Fly() string
}
2.普通 struct 实现方法
Go
type Bird struct{}
func (b Bird) Fly() string {
return "bird is flying"
}
3.直接赋值成功
Go
func main() {
var f Flyable
f = Bird{} // 自动匹配
fmt.Println(f.Fly())
}
再比如
Go
type Shape interface {
Area() float64
Perimeter() float64
}
Shape是一个接口,定义了两个方法:Area和Perimeter。- 任意类型只要实现了这两个方法,就被认为实现了
Shape接口。
实现接口: 类型通过实现接口要求的所有方法来实现接口。
Go
package main
import (
"fmt"
"math"
)
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义一个结构体
type Circle struct {
Radius float64
}
// Circle 实现 Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func main() {
c := Circle{Radius: 5}
var s Shape = c // 接口变量可以存储实现了接口的类型
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
四、接口作为参数(依赖倒置)
这是 Go 最常见、也是最"优雅"的用法。
Go
type Logger interface {
Log(msg string)
}
func Process(l Logger) {
l.Log("processing...")
}
实现 1:控制台日志
Go
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(msg string) {
fmt.Println(msg)
}
实现 2:文件日志
Go
type FileLogger struct{}
func (f FileLogger) Log(msg string) {
fmt.Println("write to file:", msg)
}
Go
Process(ConsoleLogger{})
Process(FileLogger{})
五、空接口 interface{}
Go
var x interface{}
x = 10
x = "hello"
x = true
可接收任何类型
类型断言
Go
v, ok := x.(string)
if ok {
fmt.Println(v)
}
type switch(更优雅)
Go
switch v := x.(type) {
case int:
fmt.Println("int", v)
case string:
fmt.Println("string", v)
default:
fmt.Println("unknown")
}
六、接口组合
Go
type Reader interface {
Read()
}
type Writer interface {
Write()
}
type ReadWriter interface {
Reader
Writer
}
七、动态值和动态类型
接口变量实际上包含了两部分:
- 动态类型:接口变量存储的具体类型。
- 动态值:具体类型的值。
动态值和动态类型示例:
Go
package main
import "fmt"
func main() {
var i interface{} = 42
fmt.Printf("Dynamic type: %T, Dynamic value: %v\n", i, i)
}
八、接口的零值
接口的零值是 nil。
当接口变量的动态类型和动态值都为 nil 时,接口变量为 nil。
接口零值示例:
Go
package main
import "fmt"
func main() {
var i interface{}
fmt.Println(i == nil) // 输出:true
}
九、接口值的本质
Go
var r Reader
接口值内部其实是一个 二元组:
bash
(type, value)
| 情况 | 是否为 nil |
|---|---|
| var r Reader = nil | 是 |
| var b *Bird = nil; r = b | 否(常见坑) |
经典坑
Go
var b *Bird = nil
var f Flyable = b
fmt.Println(f == nil) // false
接口不为 nil,但内部值是 nil
十、Go 标准库里的"接口哲学"
Go 标准库接口都极小:
Go
type io.Reader interface {
Read(p []byte) (n int, err error)
}
只要实现了 Read:
-
文件
-
网络
-
内存
-
压缩流
全部通用
这就是 Go 接口设计的巅峰案例