Skip to content

第07章 接口与多态 —— Go 的鸭子类型

本章目标:理解 Go 接口"隐式实现"的设计哲学,掌握类型断言、空接口、类型 switch,知道如何用接口写出可扩展的代码。


1. 接口是什么?

🌰 生活化比喻:接口是契约——只描述"能做什么",不关心"是谁做的"。

举个例子:你需要找一个"能开车"的人。

  • 你不在乎他叫小明还是小红
  • 你只在乎他会开车
go
type Driver interface {
    Drive()
}

任何只要有 Drive() 方法的类型,都自动实现了 Driver 接口。

📊 配套图解:diagrams/interface-concept.svg


2. 隐式实现(Duck Typing)

go
type Animal interface {
    Speak() string
}

type Dog struct{}
func (d Dog) Speak() string { return "汪汪" }

type Cat struct{}
func (c Cat) Speak() string { return "喵喵" }

// Dog 和 Cat 都"自动"实现了 Animal,无需声明
var a Animal = Dog{}
fmt.Println(a.Speak())   // 汪汪

a = Cat{}
fmt.Println(a.Speak())   // 喵喵

🌰 生活化比喻:"如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子"——这就是 Duck Typing。

与 Java/C# 对比

语言实现方式
Javaclass Dog implements Animal 必须显式声明
Go只要有方法就隐式实现

好处

  • 解耦:定义接口的包不需要知道实现者
  • 灵活:可以为别人写的类型补充接口实现

📊 配套图解:diagrams/duck-typing.svg


3. 接口的内部结构

接口变量在内存中是个两元组

┌─────────────────────┐
│ Type → 实际类型描述符
│ Value → 实际值的指针
└─────────────────────┘
go
var a Animal = Dog{Name: "旺财"}
// a 内部:{ Type: Dog, Value: 指向 Dog{Name:"旺财"} }

📊 配套图解:diagrams/interface-internals.svg


4. 空接口 interface{}any

interface{}(Go 1.18+ 推荐用 any)是没有任何方法的接口——所有类型都满足它,等于"任意类型"。

go
func print(v any) {
    fmt.Println(v)
}

print(42)
print("hello")
print([]int{1, 2, 3})

🌰 生活化比喻:any万能口袋,啥都能装。

⚠️ 不要滥用 any:失去类型安全,编译器帮不了你。除非确实需要"任意类型"(如标准库的 fmt.Println),否则用具体类型。


5. 类型断言(Type Assertion)

把接口"还原"成具体类型:

go
var a any = "hello"

s := a.(string)            // 断言为 string,失败会 panic
s, ok := a.(string)        // 安全断言:失败时 ok = false

完整示例:

go
func describe(v any) {
    if s, ok := v.(string); ok {
        fmt.Println("是字符串:", s)
    } else if n, ok := v.(int); ok {
        fmt.Println("是整数:", n)
    } else {
        fmt.Println("未知类型")
    }
}

6. 类型 switch

类型断言的 if-else 链可以用 switch x.(type) 优雅化:

go
func describe(v any) {
    switch x := v.(type) {
    case nil:
        fmt.Println("nil")
    case int:
        fmt.Println("int", x)
    case string:
        fmt.Println("string", x)
    case []int:
        fmt.Println("int slice", x)
    case error:
        fmt.Println("error", x.Error())
    default:
        fmt.Printf("未知类型 %T\n", x)
    }
}

📊 配套图解:diagrams/type-assertion.svg


7. 接口组合

go
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
}

这就是标准库 io.ReadWriter 的定义。


8. 著名例子:io.Readerio.Writer

Go 标准库最经典的接口设计:

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

只有一个方法,但天底下所有读/写操作都能套进去

  • 文件:*os.File
  • 网络连接:net.Conn
  • 字符串读取:strings.Reader
  • 缓冲:bytes.Buffer
  • HTTP 请求体:http.Request.Body
go
io.Copy(dst Writer, src Reader)   // 从任意 Reader 拷贝到任意 Writer

🌰 生活化比喻:io.Reader/Writer 就像通用插头——所有电器都用同一种插头标准。


9. nil 接口的"诡异"行为

go
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }

func mightFail() error {
    var p *MyError = nil
    return p   // 返回 (类型=*MyError, 值=nil) 的接口,而非 nil 接口!
}

func main() {
    err := mightFail()
    fmt.Println(err == nil)   // false !踩坑了
}

原因:接口的 nil 判断同时比较类型(*MyError)(nil) 不是 nil,因为类型部分不为空。

正确写法

go
func mightFail() error {
    var p *MyError = nil
    if p != nil {
        return p
    }
    return nil
}

📊 配套图解:diagrams/nil-interface-trap.svg

⚠️ 这是 Go 面试的高频陷阱题


10. 案例:可扩展的支付系统

go
type Payment interface {
    Pay(amount float64) error
}

type Alipay struct{}
func (a Alipay) Pay(amt float64) error {
    fmt.Printf("支付宝支付 ¥%.2f\n", amt)
    return nil
}

type WeChatPay struct{}
func (w WeChatPay) Pay(amt float64) error {
    fmt.Printf("微信支付 ¥%.2f\n", amt)
    return nil
}

func Checkout(p Payment, amt float64) error {
    return p.Pay(amt)
}

// 使用
Checkout(Alipay{}, 99.9)
Checkout(WeChatPay{}, 99.9)

新增"信用卡支付"?只要写一个有 Pay 方法的类型即可,不用修改 Checkout。这就是接口的威力。


11. 本章小结

✅ 接口只描述"能做什么",不关心"是谁做的"
✅ Go 是隐式实现——只要方法对得上就算实现
✅ 接口变量底层是 (Type, Value) 二元组
any = interface{} = 任意类型
✅ 类型断言 v.(T),安全版 v, ok := i.(T)
✅ 类型 switch 处理多种可能类型
nil 接口陷阱:(*T)(nil) != nil interface
✅ 接口组合通过嵌入实现


📂 配套资源