第二十二回:Go 语言接口 (Interface)

谁是江南第一才子?
唐伯虎
唐伯虎: 华太师要举办才艺大赛,选拔伴读书童。规则很简单:不管你是人是鬼,只要有才艺(实现方法),就能参加!这就是接口 (Interface)

📜 才艺契约 (Interface 定义)

接口不关心你是谁,只关心你会干什么。它定义了一组行为规范

package main
import "fmt"

// 定义一个"才子"接口
type Talent interface {
    Perform() string // 只要会"表演"并返回字符串,就是才子
}

🦆 鸭子类型 (Implicit Implementation)

在 Java 等语言里,你得大喊“我实现了这个接口”(implements)。但在 Go 语言里,只要你默默地做到了接口要求的事,你就是它的人!

这叫鸭子类型 (Duck Typing):如果它走起路来像鸭子,叫起来像鸭子,那它就是鸭子。

选手一:唐伯虎

type Tang struct {
    Poem string
}

// 唐伯虎实现了 Perform 方法
func (t Tang) Perform() string {
    return "桃花坞里桃花庵... " + t.Poem
}

选手二:秋香

type Qiuxiang struct {
    Smile string
}

// 秋香也实现了 Perform 方法
func (q Qiuxiang) Perform() string {
    return "回眸一笑百媚生... " + q.Smile
}
秋香
秋香: 只要我也会 Perform,那我也是 Talent 接口的一员咯?

🎭 众生平等 (多态性)

现在我们可以写一个函数,接待所有“才子”。

// 接收 Talent 接口类型的参数
func ShowTime(t Talent) {
    fmt.Println("表演开始:", t.Perform())
}

func main() {
    tang := Tang{Poem: "别人笑我太疯癫"}
    qiuxiang := Qiuxiang{Smile: "三笑留情"}

    // 只要实现了接口,就能传进去
    ShowTime(tang)     // 输出:桃花坞里桃花庵... 别人笑我太疯癫
    ShowTime(qiuxiang) // 输出:回眸一笑百媚生... 三笑留情
}

⚠️ 致命陷阱:指针实现 vs 值实现

这是新手最容易掉的坑!如果方法是用指针接收者实现的,那么只有指针才算实现了接口。

type Painter interface {
    Draw()
}

type Scholar struct {}

// 注意:这里用的是指针接收者 (s *Scholar)
func (s *Scholar) Draw() {
    fmt.Println("画了一只小鸡吃米图")
}

func main() {
    var p Painter
    s := Scholar{}
    
    // p = s  // ❌ 报错!Scholar 没有实现 Painter (是 *Scholar 实现的)
    p = &s // ✅ 正确!*Scholar 实现了 Painter
    p.Draw()
}

🎭 接口的真面目 (Type, Value)

接口变量在内存里其实有两部分:

只有当 Type 和 Value 都是 nil 时,接口才是 nil!

func main() {
    var tang *Tang = nil // 唐伯虎还没出门,是空的 (nil)
    
    // 把这个空指针给接口
    var i Talent = tang
    
    // 问:i 是 nil 吗?
    if i == nil {
        fmt.Println("接口是空的")
    } else {
        fmt.Println("接口不是空的!(虽然唐伯虎不在,但名牌还在)")
    }
    // 输出:接口不是空的!
    
    // 为什么?
    // i 的结构:(Type: *Tang, Value: nil)
    // 只要 Type 不为空,i 就不是 nil。
}
⚠️ 避坑指南: 千万别把具体的 nil 指针赋值给接口,除非你真的想让它“不为空”。

🔗 组合拳 (接口嵌入)

接口也可以像结构体一样组合。比如“江南四大才子”不仅要会“表演”,还要会“喝酒”。

type Performer interface {
    Perform()
}

type Drinker interface {
    Drink()
}

// 四大才子接口:集大成者
type TopTalent interface {
    Performer // 嵌入表演接口
    Drinker   // 嵌入喝酒接口
}

// 只有同时学会这两个技能,才配叫 TopTalent

📦 万能容器 (空接口 interface{} / any)

如果一个接口里面什么方法都没有,那岂不是所有人都实现了它?

没错!interface{} (现在 Go 1.18+ 也叫 any) 可以接收任何类型的值。就像一个万能收纳箱。

func PrintAnything(v interface{}) {
    fmt.Printf("你给了我一个: %v\n", v)
}

func main() {
    PrintAnything("秋香")  // string
    PrintAnything(9527)    // int
    PrintAnything(true)    // bool
}
注意: 虽然空接口能装万物,但拿出来用的时候,你不知道它到底是啥,容易出乱子(需要类型断言,详见第28回)。

🎯 练功房(祝枝山的才艺)

祝枝山也要参加比赛,他的才艺是“神鸟凤凰图”。请帮他实现 Talent 接口。

package main
import "fmt"

type Talent interface {
    Perform()
}

type Zhu struct {}

// 填空:实现接口方法
func (z Zhu) ______() {
    fmt.Println("这是小鸡吃米图!")
}

func main() {
    var t Talent = Zhu{}
    t.Perform()
}

任务: 填补方法名。

答案: Perform

解析: 接口定义了什么方法名,你就得实现什么方法名。