下载工作台
Go 编程实战

接口与组合

试读上半部分 · 解锁后可读全文

第 6 章 · 接口与组合

本章目标:理解 interface 隐式实现;使用 io.Reader / io.Writer 抽象 IO;用 Reporter 接口 解耦 toolkit-go 报表输出;掌握组合 embedding 复用行为;理解空接口 any 与类型断言;对照 java-dev ch06 接口与 python-dev Protocol/ABC。

学时建议:3~4 小时(含 2 小时跟练)

前置:完成 go-dev ch05;建议已学 java-dev ch06


6.1 场景说明:可插拔报表

toolkit-go~/learn-go/toolkit-go,模块 example.com/toolkit-go)ch15 毕业项目要求:读取 access 日志 → 统计慢请求 → 输出 CSV 日报。不同输出格式(CSV / JSON / 控制台)应可替换,而不改核心统计逻辑——这正是 interface 的用武之地。

internal/
├── report/
│   ├── reporter.go          # Reporter 接口 + SlowRow
│   ├── csv_reporter.go
│   ├── json_reporter.go
│   └── console_reporter.go
├── logparser/
│   └── parser.go            # 依赖 io.Reader
└── model/
    └── product.go           # 可实现 fmt.Stringer
需求抽象实现
报表格式可换ReporterCSV / JSON / Console
输入源可换io.Reader文件、字符串、HTTP body
输出目标可换io.Writer文件、Buffer、Response

6.2 接口定义与隐式实现

Go 接口是方法集合;类型无需显式 implements,只要方法集匹配即实现。

// internal/report/reporter.go
package report

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
)

type SlowRow struct {
	Path       string
	DurationMs int
	Status     int
}

type Reporter interface {
	Generate(rows []SlowRow) ([]byte, error)
}

type Storer interface {
	Save(data []byte) error
}
// internal/report/csv_reporter.go
type CSVReporter struct {
	Title string
}

func (c CSVReporter) Generate(rows []SlowRow) ([]byte, error) {
	var buf bytes.Buffer
	w := csv.NewWriter(&buf)
	_ = w.Write([]string{"path", "duration_ms", "status"})
	for _, r := range rows {
		_ = w.Write([]string{
			r.Path,
			fmt.Sprintf("%d", r.DurationMs),
			fmt.Sprintf("%d", r.Status),
		})
	}
	w.Flush()
	return buf.Bytes(), w.Error()
}
JavaGoPython
implements Reporter隐式满足方法集class X(Protocol)
接口 + 抽象类小接口 + 组合 structABC + mixin
编译期强制实现类声明编译期检查使用方duck typing

6.3 面向接口编程:RunReport

// internal/report/run.go
package report

import "fmt"

func RunReport(r Reporter, rows []SlowRow, w io.Writer) error {
	out, err := r.Generate(rows)
	if err != nil {
		return fmt.Errorf("generate: %w", err)
	}
	if len(out) == 0 {
		return nil
	}
	if w == nil {
		return fmt.Errorf("writer is nil")
	}
	_, err = w.Write(out)
	if err != nil {
		return fmt.Errorf("write: %w", err)
	}
	return nil
}

// 调用方
func Demo() {
	rows := []SlowRow{{Path: "/api/products", DurationMs: 800, Status: 200}}
	_ = RunReport(CSVReporter{Title: "daily"}, rows, os.Stdout)
}

依赖倒置:高层 RunReport 依赖 Reporter 抽象,而非具体 CSV 实现。


6.4 ConsoleReporter 与 JSONReporter

// internal/report/console_reporter.go
type ConsoleReporter struct{}

func (ConsoleReporter) Generate(rows []SlowRow) ([]byte, error) {
	for _, r := range rows {
		fmt.Printf("[SLOW] %s %dms status=%d\n", r.Path, r.DurationMs, r.Status)
	}
	return nil, nil // 已直接打印,无字节返回
}

// internal/report/json_reporter.go
type JSONReporter struct{}

func (JSONReporter) Generate(rows []SlowRow) ([]byte, error) {
	return json.Marshal(rows)
}
实现输出适用
CSVReporter表格字节文件 / 邮件附件
JSONReporterJSON 数组API / 前端
ConsoleReporterstdout本地调试

6.5 标准库接口:io.Reader / io.Writer

// internal/logparser/parser.go
package logparser

import (
	"bufio"
	"io"
	"strconv"
	"strings"
)

type SlowEntry struct {
	Path       string
	DurationMs int
	Status     int
}

// 简化格式:path|duration_ms|status
func ParseSlowRequests(r io.Reader, thresholdMs int) ([]SlowEntry, error) {
	scanner := bufio.NewScanner(r)
	var out []SlowEntry
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.Split(line, "|")
		if len(parts) != 3 {
			continue
		}
		ms, _ := strconv.Atoi(parts[1])
		status, _ := strconv.Atoi(parts[2])
		if ms >= thresholdMs {
			out = append(out, SlowEntry{
				Path: parts[0], DurationMs: ms, Status: status,
			})
		}
	}
	return out, scanner.Err()
}
接口方法常见实现
io.ReaderRead([]byte) (int, error)文件、strings.Reader、HTTP Body
io.WriterWrite([]byte) (int, error)文件、bytes.Buffer、HTTP Response
io.CloserClose() error文件、连接
fmt.StringerString() string日志打印

测试友好strings.NewReader(sample) 无需真实文件即可测 parser。


6.6 空接口 any 与类型断言

Go 1.18+ 用 any 作为 interface{} 别名。

func Describe(v any) string {
	switch x := v.(type) {
	case string:
		return "str:" + x
	case int:
		return fmt.Sprintf("int:%d", x)
	case Product: // 若在同包引用 model.Product
		return "product:" + x.Slug
	default:
		return fmt.Sprintf("unknown:%T", v)
	}
}

// 类型断言
var i any = "hello"
s, ok := i.(string)
if ok {
	_ = s
}

原则:业务代码优先小接口any 仅用于 JSON 解码、泛型边界等。


6.7 组合 struct 代替继承

// internal/report/base.go
type BaseReporter struct {
	Title string
}

type TitledCSVReporter struct {

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。