Go 语言接口
隐式实现、接口组合、空接口、以及「接受接口返回结构体」。
定义与实现
type Shape interface {
Area() float64
Perimeter() float64
}
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
type Circle struct{ R float64 }
func (c Circle) Area() float64 { return 3.14159 * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.R }
func describe(s Shape) {
fmt.Printf("面积 %.2f 周长 %.2f\n", s.Area(), s.Perimeter())
}
describe(Rect{3, 4})
describe(Circle{1})面积 12.00 周长 14.00
面积 3.14 周长 6.28Neither Rect nor Circle declares implements Shape. If the method set matches, they are Shape. That lets third-party types satisfy your interface without touching their code.
接口组合
type Reader interface{ Read(p []byte) (int, error) }
type Writer interface{ Write(p []byte) (int, error) }
// 组合
type ReadWriter interface {
Reader
Writer
}
// 标准库就是这么定义的
var _ io.ReadWriter = (*os.File)(nil) // 编译期断言The standard library's io.ReadWriter and io.ReadWriteCloser are built this way. Keep interfaces small: one method ideally, three at most. More methods means fewer types can satisfy them.
空接口与 any
// any 是 interface{} 的别名(Go 1.18+)
func printAny(v any) {
fmt.Printf("%v (%T)\n", v, v)
}
printAny(42)
printAny("hi")
printAny([]int{1})42 (int)
hi (string)
[1] ([]int)any holds anything but requires an assertion to get the concrete type back — giving up compile-time checking. Prefer concrete types or small interfaces in APIs.
接口的 nil 陷阱
type MyErr struct{}
func (e *MyErr) Error() string { return "出错了" }
func bad() error {
var e *MyErr = nil
return e // 返回的 error 不是 nil!
}
err := bad()
fmt.Println(err == nil) // false
fmt.Println(err) // 出错了false
出错了(*MyErr)(nil) has a non-nil type, so the interface is not nil. To return nil, **write return nil** — never return a typed variable holding nil.接受接口,返回结构体
// 好:参数用接口,调用方可以传任何实现
func WriteAll(w io.Writer, data []byte) error {
_, err := w.Write(data)
return err
}
// 好:返回具体类型,调用方能用到全部方法
func NewBuffer() *bytes.Buffer {
return &bytes.Buffer{}
}A community guideline: accept interfaces (flexible), return concrete types (complete information). Returning an interface strips type information and forces callers to assert.
标准库里值得记住的接口
| Interface | Method | Meaning |
|---|---|---|
error | Error() string | 错误 |
Stringer | String() string | fmt 打印时用它 |
io.Reader | Read([]byte) (int, error) | 可读源 |
io.Writer | Write([]byte) (int, error) | 可写目标 |
sort.Interface | Len / Less / Swap | 可排序 |
http.Handler | ServeHTTP | HTTP 处理器 |
type Point struct{ X, Y int }
// 实现 Stringer,fmt 就会用它
func (p Point) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
fmt.Println(Point{1, 2})(1,2)定义与实现
type Shape interface {
Area() float64
Perimeter() float64
}
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
type Circle struct{ R float64 }
func (c Circle) Area() float64 { return 3.14159 * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.R }
func describe(s Shape) {
fmt.Printf("面积 %.2f 周长 %.2f\n", s.Area(), s.Perimeter())
}
describe(Rect{3, 4})
describe(Circle{1})面积 12.00 周长 14.00
面积 3.14 周长 6.28Rect 和 Circle 都没有写 implements Shape。只要方法集合匹配,它们就是 Shape。这让第三方类型也能满足你的接口,而不需要改它的代码。
接口组合
type Reader interface{ Read(p []byte) (int, error) }
type Writer interface{ Write(p []byte) (int, error) }
// 组合
type ReadWriter interface {
Reader
Writer
}
// 标准库就是这么定义的
var _ io.ReadWriter = (*os.File)(nil) // 编译期断言标准库的 io.ReadWriter、io.ReadWriteCloser 都是这么拼出来的。接口应该小:一个方法最好,最多三个。方法越多,能实现它的类型越少。
空接口与 any
// any 是 interface{} 的别名(Go 1.18+)
func printAny(v any) {
fmt.Printf("%v (%T)\n", v, v)
}
printAny(42)
printAny("hi")
printAny([]int{1})42 (int)
hi (string)
[1] ([]int)any 能装任何值,但用的时候必须类型断言才能取出具体类型——相当于放弃了编译期检查。API 设计里尽量用具体类型或小接口,别用 any 图省事。
接口的 nil 陷阱
type MyErr struct{}
func (e *MyErr) Error() string { return "出错了" }
func bad() error {
var e *MyErr = nil
return e // 返回的 error 不是 nil!
}
err := bad()
fmt.Println(err == nil) // false
fmt.Println(err) // 出错了false
出错了(*MyErr)(nil) 的类型部分非空,所以接口本身不是 nil。函数里要返回 nil 时,**直接写 return nil**,不要返回一个值为 nil 的具体类型变量。接受接口,返回结构体
// 好:参数用接口,调用方可以传任何实现
func WriteAll(w io.Writer, data []byte) error {
_, err := w.Write(data)
return err
}
// 好:返回具体类型,调用方能用到全部方法
func NewBuffer() *bytes.Buffer {
return &bytes.Buffer{}
}这是 Go 社区的一条设计准则:参数用接口(灵活),返回值用具体类型(信息完整)。返回接口会让调用方失去类型信息,还得再断言。
标准库里值得记住的接口
| 接口 | 方法 | 含义 |
|---|---|---|
error | Error() string | 错误 |
Stringer | String() string | fmt 打印时用它 |
io.Reader | Read([]byte) (int, error) | 可读源 |
io.Writer | Write([]byte) (int, error) | 可写目标 |
sort.Interface | Len / Less / Swap | 可排序 |
http.Handler | ServeHTTP | HTTP 处理器 |
type Point struct{ X, Y int }
// 实现 Stringer,fmt 就会用它
func (p Point) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
fmt.Println(Point{1, 2})(1,2)