方法与接口

翻译整理自 A Tour of Go - Methods and interfaces 与 Effective Go - Interfaces and other types。

1. 方法定义

Go 通过接收者(receiver) 为类型绑定方法:

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

调用:

r := Rectangle{Width: 3, Height: 4}
fmt.Println(r.Area()) // 12

2. 指针接收者 vs 值接收者

// 值接收者:方法操作的是副本
func (r Rectangle) Scale(factor float64) Rectangle {
    return Rectangle{r.Width * factor, r.Height * factor}
}

// 指针接收者:方法可以修改原值
func (r *Rectangle) ScaleMut(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

选择建议:

  • 需要修改接收者状态:用指针
  • 结构体较大,避免每次拷贝:用指针
  • 一致性:一个类型的方法建议统一选其一
  • 小的不可变值类型(如 time.Time):可用值

3. 方法可以定义在任意包内类型

只能为当前包内定义的类型定义方法。可以通过别名绕开:

type MyString string

func (s MyString) Shout() MyString {
    return MyString(strings.ToUpper(string(s)) + "!")
}

4. 接口

接口是方法签名的集合。类型只要实现了接口中所有方法,即自动满足该接口(鸭子类型,无需显式声明):

type Shape interface {
    Area() float64
    Perimeter() float64
}

任何实现了 Area() 和 Perimeter() 的类型都满足 Shape:

func Describe(s Shape) {
    fmt.Println(s.Area(), s.Perimeter())
}

Describe(Rectangle{3, 4}) // 12 14

空接口与 any

interface{}(Go 1.18+ 起别名为 any)接受任意类型:

func Print(v any) {
    fmt.Println(v)
}

5. 类型断言与 type switch

var i interface{} = "hello"

s, ok := i.(string) // 带检查
if ok {
    fmt.Println(s)
}

// type switch
switch v := i.(type) {
case string:
    fmt.Println("string:", v)
case int:
    fmt.Println("int:", v)
default:
    fmt.Printf("%T\n", v)
}

6. Stringer 接口

实现 String() 方法的类型会被 fmt 自动调用:

type Person struct{ Name string; Age int }

func (p Person) String() string {
    return fmt.Sprintf("%s(%d)", p.Name, p.Age)
}

fmt.Println(Person{"Alice", 30}) // Alice(30)

7. error 与 error 接口

error 是内置接口:

type error interface {
    Error() string
}

自定义错误:

type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code=%d msg=%s", e.Code, e.Msg)
}

8. 接口组合

接口可嵌入其他接口:

type ReadWriter interface {
    io.Reader
    io.Writer
}

io.ReadWriter 即要求同时满足 Reader 和 Writer。

9. 接口设计原则

Accept interfaces, return structs. 函数参数尽量用接口以提升可替换性,返回值用具体类型便于调用方使用。

小结

Go 的方法与接口以隐式实现为核心,避免了 Java 那种显式 implements,让代码解耦更自然。下一章学习 Go 的杀手锏:并发。