Go: Go 方法与接口
最后更新:2026-08-26
方法是带接收者的函数,接口是方法签名的集合——Go 用简洁的语法实现了面向对象的"行为抽象",却没有类继承的复杂性。
Go 没有 class,但有 method;没有 implements 关键字,但有 duck typing 的接口。这节课你将掌握 Go 面向对象的全部核心,并能用接口构建可切换的多支付网关系统。
1. 你将学到
- 方法定义(值接收者 vs 指针接收者)
- 接口定义与隐式实现(duck typing)
- 空接口
interface{}的用途 - 类型断言与类型 switch
- 接口组合(嵌入接口)
io.Reader/io.Writer标准接口- 用接口构建可切换的多支付网关
2. 一个电商支付工程师的真实故事
(1) 痛点:switch 分支写死,新增支付网关得改核心代码
Charlie 是电商平台的后端工程师,他维护着一个支付模块:
"我们支持 Stripe,现在要加 PayPal。但支付代码里全是
if gateway == "stripe",加一个 PayPal 得把整个文件重写一遍。"
他打开前任写的代码:
// 坏代码:支付逻辑硬编码
func charge(amount float64, gateway string) error {
switch gateway {
case "stripe":
// Stripe 的 HTTP API 调用...
return stripeCharge(amount)
case "paypal":
// 加 PayPal 就得在这里再添一个 case
return nil
default:
return fmt.Errorf("unknown gateway: %s", gateway)
}
}
每加一个支付网关,就得改 charge 函数——违反开闭原则(对扩展开放,对修改关闭)。
(2) Go 的解法:接口隐式实现
// payment.go
package main
import "fmt"
// 定义支付接口
type PaymentGateway interface {
Charge(amount float64) error
Refund(transactionID string) error
}
// Stripe 实现(不用写 implements!)
type Stripe struct {
apiKey string
}
func (s Stripe) Charge(amount float64) error {
fmt.Printf("Stripe: charged $%.2f\n", amount)
return nil
}
func (s Stripe) Refund(txID string) error {
fmt.Printf("Stripe: refunded %s\n", txID)
return nil
}
// PayPal 实现
type PayPal struct {
email string
}
func (p PayPal) Charge(amount float64) error {
fmt.Printf("PayPal: charged $%.2f\n", amount)
return nil
}
func (p PayPal) Refund(txID string) error {
fmt.Printf("PayPal: refunded %s\n", txID)
return nil
}
// 消费代码:只依赖接口,不依赖具体实现
func processPayment(gw PaymentGateway, amount float64) error {
return gw.Charge(amount)
}
func main() {
stripe := Stripe{apiKey: "sk_test_xxx"}
paypal := PayPal{email: "merchant@example.com"}
// 同样的 processPayment function,可以传不同的实现
processPayment(stripe, 99.99)
processPayment(paypal, 49.99)
}
输出:
Stripe: charged $99.99
PayPal: charged $49.99
(3) 收益:开闭原则
| 方式 | 新增网关 | 修改核心代码 | 风险 |
|---|---|---|---|
| switch 硬编码 | 改 charge function |
✅ 需要 | 🔴 高 |
| 接口抽象 | 新建 struct 实现接口 | ❌ 不需要 | 🟢 低 |
implements 更灵活:你甚至可以给第三方包的类型实现接口(在外部包中定义)。
3. 方法定义
(1) method = 带接收者的函数
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 方法:接收者 (r Rectangle) 在 func 关键字和函数名之间
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("Area: %.2f\n", rect.Area()) // Area: 50.00
}
(2) 值接收者 vs 指针接收者
package main
import "fmt"
type Counter struct {
Value int
}
// 值接收者:操作副本,不影响原对象
func (c Counter) IncrementValue() Counter {
c.Value++
return c
}
// 指针接收者:直接修改原对象
func (c *Counter) IncrementPointer() {
c.Value++
}
func main() {
c := Counter{Value: 10}
// 值接收者必须用返回值
c = c.IncrementValue()
fmt.Printf("值接收者后:%d\n", c.Value)
// 指针接收者直接修改
c.IncrementPointer()
fmt.Printf("指针接收者后:%d\n", c.Value)
}
输出:
值接收者后:11
指针接收者后:12
▶ 示例:值/指针接收者选型
package main
import "fmt"
type User struct {
Name string
Age int
}
// 值接收者:适合小对象、只读操作
func (u User) Info() string {
return fmt.Sprintf("%s (%d)", u.Name, u.Age)
}
// 指针接收者:适合大对象、修改操作
func (u *User) SetName(name string) {
u.Name = name
}
type LargeData struct {
data [1000]int
}
// 大结构体必须用指针接收者(避免复制 1000 个 int)
func (l *LargeData) Process() int {
sum := 0
for _, v := range l.data {
sum += v
}
return sum
}
func main() {
u := User{Name: "Alice", Age: 28}
u.SetName("Alice Smith")
fmt.Println(u.Info())
ld := LargeData{}
for i := 0; i < 1000; i++ {
ld.data[i] = i
}
fmt.Printf("Sum: %d\n", ld.Process())
}
输出:
Alice Smith (28)
Sum: 499500
(3) 值 vs 指针接收者选择指南
| 场景 | 接收者类型 | 原因 |
|---|---|---|
| 方法不修改接收者 | 值或指针均可 | 值接收者更安全(无副作用) |
| 方法需要修改接收者 | 指针 | 值接收者修改的是副本 |
| 结构体很大(> 100 bytes) | 指针 | 避免复制大对象 |
| 接收者是 map/slice/func | 值(它们是引用类型) | 本身就是引用 |
| 类型是基本类型 | 值(无需指针) | 小,复制开销低 |
4. 接口:隐式实现(Duck Typing)
(1) 接口定义
// 定义接口:一组方法签名
type Stringer interface {
String() string
}
▶ 示例:隐式实现
package main
import "fmt"
// 1. 定义接口
type Speaker interface {
Speak() string
}
// 2. 定义两个 struct,都实现 Speak 方法
type Dog struct{ Name string }
func (d Dog) Speak() string {
return fmt.Sprintf("%s says: Woof!", d.Name)
}
type Cat struct{ Name string }
func (c Cat) Speak() string {
return fmt.Sprintf("%s says: Meow!", c.Name)
}
// 3. 消费函数:接受接口
func greet(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "Buddy"}
cat := Cat{Name: "Whiskers"}
// Dog 和 Cat 都隐式实现了 Speaker,无需 implements 关键字
greet(dog)
greet(cat)
}
输出:
Buddy says: Woof!
Whiskers says: Meow!
(2) 接口值:动态类型 + 动态值
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string {
return fmt.Sprintf("%s says: Woof!", d.Name)
}
func main() {
var s Speaker // 接口变量,默认 nil
fmt.Printf("nil: %T, %v\n", s, s)
s = Dog{Name: "Buddy"} // 接口存储了动态类型和动态值
fmt.Printf("type=%T, value=%v\n", s, s)
}
输出:
nil: <nil>, <nil>
type=main.Dog, value={Buddy}
5. 空接口 interface{} 与类型断言
(1) 空接口:任意类型
package main
import "fmt"
type Dog struct {
Name string
}
// 空接口可以存储任何类型
func describe(v interface{}) {
fmt.Printf("type=%T, value=%v\n", v, v)
}
func main() {
describe(42)
describe("hello")
describe(3.14)
describe(Dog{Name: "Buddy"})
}
输出:
type=int, value=42
type=string, value=hello
type=float64, value=3.14
type=main.Dog, value={Buddy}
▶ 示例:类型断言(comma-ok 语法)
package main
import "fmt"
func printValue(v interface{}) {
// 类型断言:提取底层值
if s, ok := v.(string); ok {
fmt.Printf("String: %s (len=%d)\n", s, len(s))
return
}
if n, ok := v.(int); ok {
fmt.Printf("Int: %d (double=%d)\n", n, n*2)
return
}
fmt.Printf("Unknown type: %T = %v\n", v, v)
}
func main() {
printValue("hello")
printValue(42)
printValue(3.14)
}
输出:
String: hello (len=5)
Int: 42 (double=84)
Unknown type: float64 = 3.14
(2) 类型 switch
package main
import "fmt"
func inspect(v interface{}) {
switch val := v.(type) {
case string:
fmt.Printf("string: %q (len=%d)\n", val, len(val))
case int:
fmt.Printf("int: %d\n", val)
case float64:
fmt.Printf("float64: %.2f\n", val)
case bool:
fmt.Printf("bool: %v\n", val)
default:
fmt.Printf("unknown: %T\n", val)
}
}
func main() {
inspect("hello")
inspect(42)
inspect(3.14)
inspect(true)
inspect([]int{1, 2, 3})
}
输出:
string: "hello" (len=5)
int: 42
float64: 3.14
bool: true
unknown: []int
(3) 类型断言 vs 类型 switch
| 场景 | 推荐 |
|---|---|
| 判断是否为某一种类型 | 类型断言 v.(T) |
| 判断多种类型 | 类型 switch v.(type) |
| 只需要检测(不用值) | _, ok := v.(T) |
6. 接口组合
(1) 嵌入接口创建新接口
package main
import "fmt"
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合 Reader 和 Writer,形成新接口
type ReadWriter interface {
Reader
Writer
}
// 实现
type File struct{}
func (f File) Read(p []byte) (n int, err error) {
return len(p), nil
}
func (f File) Write(p []byte) (n int, err error) {
return len(p), nil
}
func main() {
var rw ReadWriter = File{}
buf := make([]byte, 10)
rw.Read(buf)
rw.Write(buf)
fmt.Println("ReadWriter 组合接口工作正常")
}
▶ 示例:接口组合实战
package main
import "fmt"
type Logger interface {
Log(message string)
}
type Notifier interface {
Notify(message string)
}
// 组合
type LoggerNotifier interface {
Logger
Notifier
}
type ConsoleService struct{}
func (c ConsoleService) Log(message string) {
fmt.Printf("[LOG] %s\n", message)
}
func (c ConsoleService) Notify(message string) {
fmt.Printf("[NOTIFY] %s\n", message)
}
func main() {
var svc LoggerNotifier = ConsoleService{}
svc.Log("系统启动")
svc.Notify("用户 Alice 登录")
}
输出:
[LOG] 系统启动
[NOTIFY] 用户 Alice 登录
(2) 接口组合方式速查
| 组合方式 | 语法 | 说明 |
|---|---|---|
| 嵌入单个接口 | type A interface { B } |
A 包含 B 的全部方法 |
| 嵌入多个接口 | type A interface { B; C } |
A 包含 B+C 的全部方法 |
| 嵌入 + 新增方法 | type A interface { B; C; Do() } |
A 包含 B+C+Do 的方法 |
7. io.Reader / io.Writer 标准接口
(1) 标准库最核心的两个接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
▶ 示例:多种类型实现 Reader
package main
import (
"fmt"
"io"
"strings"
)
func printReader(r io.Reader) {
buf := make([]byte, 8)
for {
n, err := r.Read(buf)
if err == io.EOF {
break
}
fmt.Printf("read: %q\n", buf[:n])
}
}
func main() {
// strings.Reader 实现了 io.Reader
fmt.Println("=== strings.Reader ===")
printReader(strings.NewReader("hello world"))
// 还可以用 bytes.Reader、os.File 等
}
输出:
=== strings.Reader ===
read: "hello wo"
read: "rld"
(2) io.Reader + io.Writer 组合(标准库的 chain)
package main
import (
"fmt"
"io"
"strings"
)
func main() {
// 用 io.Reader + io.Writer 实现拷贝
reader := strings.NewReader("hello Go interfaces")
writer := &strings.Builder{}
// io.Copy 接受任何 Reader 和 Writer
n, _ := io.Copy(writer, reader)
fmt.Printf("copied %d bytes: %q\n", n, writer.String())
}
输出:
copied 19 bytes: "hello Go interfaces"
(3) 实现 io.Reader 的标准接口清单
| 类型 | package | 实现了 |
|---|---|---|
strings.Reader |
strings | Reader |
bytes.Reader |
bytes | Reader |
os.File |
os | Reader + Writer |
bytes.Buffer |
bytes | Reader + Writer |
net.Conn |
net | Reader + Writer |
gzip.Reader |
compress/gzip | Reader |
8. 完整示例:多支付网关抽象
把本课所有知识点串起来,构建一个完整的支付系统:
// payment_system.go
package main
import (
"fmt"
"time"
)
// ---------- 接口定义 ----------
type PaymentGateway interface {
Charge(amount float64) (string, error) // 返回交易 ID
Refund(transactionID string) error
Name() string
}
// 日志记录接口(组合示例)
type TransactionLogger interface {
Log(transactionID, gateway string, amount float64, success bool)
}
// ---------- Stripe 实现 ----------
type Stripe struct {
apiKey string
}
func (s Stripe) Charge(amount float64) (string, error) {
txID := fmt.Sprintf("STRIPE-%s", s.txID())
fmt.Printf("[Stripe] charging $%.2f -> %s\n", amount, txID)
return txID, nil
}
func (s Stripe) Refund(txID string) error {
fmt.Printf("[Stripe] refunding %s\n", txID)
return nil
}
func (s Stripe) Name() string {
return "Stripe"
}
func (Stripe) txID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
// ---------- PayPal 实现 ----------
type PayPal struct {
email string
}
func (p PayPal) Charge(amount float64) (string, error) {
txID := fmt.Sprintf("PP-%s", p.txID())
fmt.Printf("[PayPal] charging $%.2f -> %s\n", amount, txID)
return txID, nil
}
func (p PayPal) Refund(txID string) error {
fmt.Printf("[PayPal] refunding %s\n", txID)
return nil
}
func (p PayPal) Name() string {
return "PayPal"
}
func (PayPal) txID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
// ---------- Alipay 实现 ----------
type Alipay struct {
appID string
}
func (a Alipay) Charge(amount float64) (string, error) {
txID := fmt.Sprintf("ALI-%s", a.txID())
fmt.Printf("[Alipay] charging $%.2f -> %s\n", amount, txID)
return txID, nil
}
func (a Alipay) Refund(txID string) error {
fmt.Printf("[Alipay] refunding %s\n", txID)
return nil
}
func (a Alipay) Name() string {
return "Alipay"
}
func (Alipay) txID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
// ---------- 日志实现(空接口 + 类型断言 示例)----------
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(transactionID, gateway string, amount float64, success bool) {
status := "SUCCESS"
if !success {
status = "FAILED"
}
fmt.Printf("[%s] %s | %s | $%.2f | %s\n",
status, transactionID, gateway, amount, time.Now().Format(time.RFC3339))
}
// ---------- 支付服务 ----------
type PaymentService struct {
gateway PaymentGateway
logger TransactionLogger
}
func NewPaymentService(gw PaymentGateway, logger TransactionLogger) *PaymentService {
return &PaymentService{gateway: gw, logger: logger}
}
func (s *PaymentService) Charge(amount float64) error {
txID, err := s.gateway.Charge(amount)
if err != nil {
s.logger.Log("", s.gateway.Name(), amount, false)
return err
}
s.logger.Log(txID, s.gateway.Name(), amount, true)
return nil
}
func (s *PaymentService) SwitchGateway(gw PaymentGateway) {
fmt.Printf("\n切换支付网关:%s -> %s\n", s.gateway.Name(), gw.Name())
s.gateway = gw
}
// ---------- main ----------
func main() {
logger := ConsoleLogger{}
stripe := Stripe{apiKey: "sk_test_xxx"}
paypal := PayPal{email: "merchant@example.com"}
alipay := Alipay{appID: "2025xxxx"}
// 初始用 Stripe
service := NewPaymentService(stripe, logger)
service.Charge(99.99)
service.Charge(49.99)
// 运行时切换为 PayPal(接口带来的灵活性)
service.SwitchGateway(paypal)
service.Charge(199.99)
// 再切换为 Alipay
service.SwitchGateway(alipay)
service.Charge(299.99)
}
预期输出:
[Stripe] charging $99.99 -> STRIPE-1741500000000
[SUCCESS] STRIPE-1741500000000 | Stripe | $99.99 | 2026-07-08T10:00:00Z
[Stripe] charging $49.99 -> STRIPE-1741500000001
[SUCCESS] STRIPE-1741500000001 | Stripe | $49.99 | 2026-07-08T10:00:00Z
切换支付网关:Stripe -> PayPal
[PayPal] charging $199.99 -> PP-1741500000002
[SUCCESS] PP-1741500000002 | PayPal | $199.99 | 2026-07-08T10:00:00Z
切换支付网关:PayPal -> Alipay
[Alipay] charging $299.99 -> ALI-1741500000003
[SUCCESS] ALI-1741500000003 | Alipay | $299.99 | 2026-07-08T10:00:00Z
classDiagram
class PaymentGateway {
<<interface>>
+Charge(amount float64) (string, error)
+Refund(transactionID string) error
+Name() string
}
class Stripe {
-apiKey string
+Charge(amount float64) (string, error)
+Refund(transactionID string) error
+Name() string
}
class PayPal {
-email string
+Charge(amount float64) (string, error)
+Refund(transactionID string) error
+Name() string
}
class Alipay {
-appID string
+Charge(amount float64) (string, error)
+Refund(transactionID string) error
+Name() string
}
class PaymentService {
-gateway PaymentGateway
-logger TransactionLogger
+Charge(amount float64) error
+SwitchGateway(gw PaymentGateway)
}
PaymentGateway <|.. Stripe : 隐式实现
PaymentGateway <|.. PayPal : 隐式实现
PaymentGateway <|.. Alipay : 隐式实现
PaymentService o--> PaymentGateway : 依赖接口(策略模式)
PaymentService 的 gateway 字段是接口类型,不是具体类型。接口变量可以存储任何实现了该接口的值——这是 Go 实现策略模式(Strategy Pattern)的基础。
❓ 常见问题
implements 关键字。这意味着:(1) 第三方包的类型也能实现你定义的接口;(2) 一个类型可以实现多个完全不相关的接口。interface{} 有什么用?fmt.Println(a ...interface{});(2) map 存不同类型值 map[string]interface{};(3) 数据反序列化(JSON 解析到 interface{})。v, ok := x.(T)。ok 为 false 时不会 panic。如果不用 comma-ok,断言失败会 panic:v := x.(string) 当 x 不是 string 时会 panic。err != nil 判断是否读完,必须用 err == io.EOF。📖 小节
- 方法 = 带接收者的函数,接收者可以是值或指针
- 值接收者操作副本,指针接收者修改原对象
- 接口是方法签名的集合,Go 用隐式实现(duck typing)
- 空接口
interface{}表示任意类型 - 类型断言
v.(T)提取接口的动态值,comma-ok 避免 panic - 接口组合通过嵌入接口创建新接口(
type A interface { B; C }) io.Reader/io.Writer是 Go 标准库最核心的两个接口- 接口让代码遵循"开闭原则"——对扩展开放,对修改关闭
📝 作业
-
基础题(难度⭐):定义
Shape接口(Area() float64),实现Circle(半径)和Rectangle(宽高)两个 struct,计算面积并打印。 -
进阶题(难度⭐⭐):实现一个
Cache接口(Get(key string) (interface{}, bool)/Set(key string, value interface{})),分别用map[string]interface{}和内存限制版(最多 10 个 key)实现两种策略。要求用指针接收者。 -
挑战题(难度⭐⭐⭐):实现一个可拔插存储后端:定义
Store接口(Save(key string, data []byte) error/Load(key string) ([]byte, error)/Delete(key string) error),分别实现MemoryStore(map 存储)和FileStore(os.WriteFile/os.ReadFile文件存储),最后用一个BackupService在两种存储间同步数据。