Go 的隐式接口、空接口与面向接口编程


一、接口基础

1.1 接口定义

// 接口是一组方法签名的集合
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 组合多个接口
type ReadWriter interface {
    Reader
    Writer
}

1.2 隐式实现(鸭子类型)

Go 的接口是隐式实现的——不需要 implements 关键字:

// 只要一个类型实现了接口的所有方法,它就自动满足该接口
type MyReader struct {
    data []byte
    pos  int
}

func (r *MyReader) Read(p []byte) (n int, err error) {
    // 实现 Read 方法
    if r.pos >= len(r.data) {
        return 0, io.EOF
    }
    n = copy(p, r.data[r.pos:])
    r.pos += n
    return n, nil
}

// MyReader 自动实现了 io.Reader 接口,无需声明
var r io.Reader = &MyReader{}  // ✅ 编译通过

Go 的哲学:行为决定类型,而不是声明决定类型。 如果它像鸭子一样走路、像鸭子一样叫,那它就是鸭子。


二、标准库中的经典接口

2.1 error 接口

// 最简单的接口之一
type error interface {
    Error() string
}

// 任何实现 Error() string 的类型都是 error
type NotFoundError struct {
    Resource string
}

func (e *NotFoundError) Error() string {
    return e.Resource + " not found"
}

2.2 io.Reader / io.Writer

// 数据源的抽象(读取器)
type Reader interface {
    Read(p []byte) (n int, err error)
}

// 数据目的地的抽象(写入器)
type Writer interface {
    Write(p []byte) (n int, err error)
}

// 这两个接口极其强大:文件、网络、内存缓冲、压缩/解压...
// 所有实现了 Reader 的东西都可以互相配合

2.3 fmt.Stringer

// 类似其他语言的 toString()
type Stringer interface {
    String() string
}

// 实现
type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

// 自动被 fmt.Println 使用
p := Point{3, 4}
fmt.Println(p)  // (3, 4)

2.4 sort.Interface

// 需要实现三个方法才能排序
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

// 实现
type People []Person

func (p People) Len() int           { return len(p) }
func (p People) Less(i, j int) bool { return p[i].Age < p[j].Age }
func (p People) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// 使用
sort.Sort(People(people))

三、空接口与类型断言

3.1 空接口(any)

// 空接口可以持有任何类型的值
var x interface{} = "hello"
var y interface{} = 42
var z interface{} = Point{1, 2}

// Go 1.18+ 中 any 是 interface{} 的别名
var a any = "hello"

3.2 类型断言(Type Assertion)

var i interface{} = "hello"

// 安全断言(带 ok 检查)
s, ok := i.(string)
if ok {
    fmt.Println(s)       // hello
} else {
    fmt.Println("not a string")
}

// 不安全断言(失败时 panic)
s := i.(string)  // 如果 i 不是 string → panic

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

3.3 空接口的使用场景

// 场景1:通用容器(类似 Python 的 list / Java 的 List<Object>)
type Queue struct {
    items []any
}

func (q *Queue) Push(item any) {
    q.items = append(q.items, item)
}

func (q *Queue) Pop() any {
    item := q.items[0]
    q.items = q.items[1:]
    return item
}

// 场景2:JSON 解析(不知道结构时)
var data any
json.Unmarshal(jsonBytes, &data)

// ⚠️ 空接口失去了类型安全,尽量少用
// 更好的方式是使用泛型(Go 1.18+)

四、接口组合

Go 通过接口嵌入实现接口组合,标准库大量使用这种模式:

// io 包的接口设计模式
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Closer interface { Close() error }

// 组合
type ReadCloser interface {
    Reader
    Closer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// 小接口组合成大接口——Go 的设计哲学
// 每个接口只定义 1-3 个方法
// "The bigger the interface, the weaker the abstraction."

五、接口底层原理(interface 内部结构)

5.1 接口的内存表示

接口值 = {type, data}
          │     │
          │     └─ 指向实际值的指针
          └─ 类型信息(itab)

空接口(interface{}):只用 {type, data},没有方法表
非空接口:用 {type, itab},itab 包含方法表指针

5.2 接口的性能影响

// 接口调用有间接寻址开销(通过 itab 调用方法)
// 对于高频调用的小方法,接口可能有性能损失
type Small struct{ x int }

func (s Small) Get() int { return s.x }

// 直接调用比接口调用快(热路径考虑)
var s Small = Small{42}

// 直接调用
s.Get()

// 通过接口调用
var iface interface{ Get() int } = s
iface.Get()  // 间接调用,略慢

六、面向接口编程示例

6.1 依赖注入

// 定义接口(在消费方定义)
type UserStore interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

// 实现(在提供方实现)
type MySQLUserStore struct {
    db *sql.DB
}

func (s *MySQLUserStore) FindByID(id int) (*User, error) {
    // MySQL 实现
}

func (s *MySQLUserStore) Save(user *User) error {
    // MySQL 实现
}

// Service 只依赖接口,不依赖具体实现
type UserService struct {
    store UserStore  // 依赖接口
}

func NewUserService(store UserStore) *UserService {
    return &UserService{store: store}
}

// 测试时可以轻松 mock
type MockUserStore struct {
    users map[int]*User
}

func (m *MockUserStore) FindByID(id int) (*User, error) {
    user, ok := m.users[id]
    if !ok {
        return nil, ErrNotFound
    }
    return user, nil
}

func (m *MockUserStore) Save(user *User) error {
    m.users[user.ID] = user
    return nil
}

// 测试
mock := &MockUserStore{users: map[int]*User{1: {ID: 1, Name: "Test"}}}
svc := NewUserService(mock)
user, _ := svc.FindByID(1)

6.2 接口最佳实践

规则说明
在消费方定义接口谁使用接口,谁定义接口
接口尽量小1-3 个方法最佳,遵循 io.Reader 的设计
不要为了测试而创建接口如果只有一个实现,不需要接口
返回具体类型,接受接口参数func NewService(db Database) 而非 func NewService(db interface{})
空接口 any 是最后手段优先考虑泛型或具体类型

七、本节要点速查

概念要点
隐式实现不需要 implements,实现方法即满足接口
接口组合通过嵌入组合小接口为大接口
空接口 anyinterface{} 的别名,可持有任意值
类型断言v, ok := i.(Type),安全检查不 panic
类型 switchswitch v := i.(type) 匹配多种类型
面向接口编程消费方定义接口,实现方提供具体类型
接口小为美方法越少越好,io.Reader 是典范
itab接口内部的类型+方法表,有间接调用开销

上一篇:06-函数与方法 下一篇:08-包与模块