包系统 - Go 的模块化组织

← 返回基础概念

包是 Go 代码组织和复用的基本单位,理解包系统对于构建可维护的项目至关重要。

包基础

package 声明

// main.go
package main  // 可执行程序的入口包

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
// utils/math.go
package utils  // 自定义包

func Add(a, b int) int {
    return a + b
}

规则 每个 .go 文件必须以 package 声明开头。

导入包

package main

import (
    "fmt"           // 标准库
    "net/http"      // 标准库
    "myproject/utils"  // 本地包
    "github.com/gin-gonic/gin"  // 第三方包
)

func main() {
    fmt.Println("Hello")
    resp, _ := http.Get("https://example.com")
    sum := utils.Add(1, 2)
    _ = resp
    _ = sum
}

包导入关系图

包的可见性

导出规则

// utils/calculator.go
package utils

// 公开函数(首字母大写)
func Add(a, b int) int {
    return a + b
}

// 私有函数(首字母小写)
func subtract(a, b int) int {
    return a - b
}

// 公开类型
type Calculator struct {
    Result int  // 公开字段
    steps  int  // 私有字段
}

// 私有类型
type debugInfo struct {
    line int
    file string
}
// main.go
package main

import "myproject/utils"

func main() {
    // ✅ 可以访问公开函数
    sum := utils.Add(1, 2)

    // ❌ 无法访问私有函数
    // diff := utils.subtract(5, 3)

    // ✅ 可以访问公开类型
    calc := utils.Calculator{}
    calc.Result = 10

    // ❌ 无法访问私有字段
    // calc.steps = 5
    _ = sum
    _ = calc
}

init 函数

init 函数基础

package main

import "fmt"

// init 函数在 main 之前自动执行
func init() {
    fmt.Println("初始化中...")
}

func init() {
    fmt.Println("再次初始化...")
}

func main() {
    fmt.Println("主程序开始")
}

// 输出顺序:
// 初始化中...
// 再次初始化...
// 主程序开始

init 函数应用

// config/config.go
package config

var (
    DatabaseURL string
    DebugMode   bool
)

func init() {
    // 从环境变量加载配置
    DatabaseURL = "localhost:5432"
    DebugMode = true
}
// main.go
package main

import (
    "fmt"
    "myproject/config"
)

func main() {
    // config 已经初始化
    fmt.Println("数据库:", config.DatabaseURL)
}

注意 init 函数的执行顺序

  1. 同一文件内:按声明顺序
  2. 同一包内:按文件名字母顺序
  3. 不同包间:按导入顺序

包别名

导入别名

package main

import (
    fmt2 "fmt"           // 别名
    str "strings"        // 简短别名
    _ "image/png"        // 空白标识符(仅执行 init)
    . "math"             // 点导入(直接访问)
)

func main() {
    fmt2.Println("使用别名")
    str.HasPrefix("hello", "he")

    // 点导入后直接使用,无需包前缀
    println(Pi)  // 而不是 math.Pi
}

使用场景

别名类型用途示例
正常别名避免命名冲突import fmt2 "fmt"
简短别名减少输入import str "strings"
空白导入仅执行 initimport _ "database/sql"
点导入直接访问import . "math"

依赖管理

go.mod 文件

// go.mod
module myproject

go 1.26

require (
    github.com/gin-gonic/gin v1.10.0
    github.com/stretchr/testify v1.9.0
)

go mod 命令

# ===== 基础操作 =====

# 初始化模块
go mod init myproject

# 下载依赖到本地缓存
go mod download

# 整理依赖(添加缺失的、移除未使用的)
go mod tidy

# 验证依赖完整性
go mod verify

# 制作 vendor 副本
go mod vendor

# ===== 编辑操作 =====

# 添加依赖
go get github.com/gin-gonic/gin

# 添加特定版本
go get github.com/gin-gonic/gin@v1.9.0

# 添加依赖到 require 块(不安装)
go get -d github.com/gin-gonic/gin

# 编辑 go.mod(添加 replace 指令)
go mod edit -replace=oldpkg=newpkg

# 编辑 go.mod(删除 replace 指令)
go mod edit -dropreplace=oldpkg

# ===== 查询操作 =====

# 查看所有依赖
go list -m all

# 查看某个模块信息
go list -m github.com/gin-gonic/gin

# 查看模块可用版本
go list -m -versions github.com/gin-gonic/gin

# 打印模块依赖图
go mod graph

# 解释为什么需要某个包
go mod why github.com/gin-gonic/gin

依赖版本管理

# 升级所有依赖
go get -u ./...

# 升级特定包
go get -u github.com/gin-gonic/gin

# 查看可用版本
go list -m -versions github.com/gin-gonic/gin

# 更新 go.mod 和 go.sum
go mod tidy

replace 指令

replace 指令用于将依赖包替换为本地路径或其他版本,常用于本地模块开发。

// go.mod
module myproject

go 1.26

require (
    github.com/example/mypackage v1.0.0
)

// 替换为本地路径
replace github.com/example/mypackage => ../mypackage

// 或替换为特定版本
// replace github.com/example/mypackage => github.com/example/mypackage v1.1.0

// 或替换为 fork 版本
// replace github.com/example/mypackage => github.com/myfork/mypackage v1.0.0
# 使用 replace 后需要整理依赖
go mod tidy

# 使用 go mod edit 添加 replace 指令
go mod edit -replace github.com/example/mypackage=../mypackage

# 查看完整的模块依赖图
go mod graph

# 查看为什么需要某个依赖
go mod why github.com/example/mypackage

go.work 工作区

go.work 是 Go 1.18+ 引入的多模块工作区模式,用于在本地开发多个相关联的模块。

// go.work
go 1.26

use (
    ./app
    ./mypackage
    ./utils
)
项目结构/
├── go.work
├── app/           # 主应用
│   ├── go.mod
│   └── main.go
├── mypackage/     # 被引用的本地包
│   ├── go.mod
│   └── package.go
└── utils/         # 被引用的本地包
    ├── go.mod
    └── util.go
# 创建工作区
go work init

# 添加模块到工作区
go work use ./app ./mypackage

# 同步工作区
go work sync
优势

go.work vs replace

多模块支持:可以同时开发多个本地模块 • 自动发现:无需在每个模块中配置 replace • IDE 友好:IDE 可以正确解析和跳转 • 团队协作:go.work 可以提交到仓库,团队成员共享配置

注意

replace 指令仅在当前模块作为主模块时生效。当你的模块被其他项目引用为依赖时,replace 规则不会被继承

如果需要发布包含 replace 的包,考虑使用 go.work 工作区模式。

包设计原则

包职责单一

// ✅ 好的设计:职责单一
package user
    - user.go       // 用户类型
    - repository.go // 数据访问
    - service.go    // 业务逻辑

// ❌ 不好的设计:职责混乱
package utils
    - user.go       // 用户相关
    - db.go         // 数据库相关
    - http.go       // HTTP 相关

包命名约定

// ✅ 推荐:简短、小写、单个单词
package fmt
package http
package strings

// ✅ 包名与目录名一致
// 目录: myproject/user
// 文件: user.go
package user

// ❌ 避免:下划线、混合大小写、过长
package my_utils
package myPackage
package verylongpackagename

包内部组织

user/
├── user.go          // 主要类型定义
├── user_test.go     // 测试文件
├── example_test.go  // 示例代码
└── doc.go           // 包文档(可选)

标准库包

常用标准库

用途常用函数
fmt格式化 I/OPrint, Printf, Sprintf
strings字符串操作Contains, HasPrefix, Join
strconv字符串转换Atoi, Itoa, FormatInt
net/httpHTTP 客户端/服务端Get, Post, HandleFunc
encoding/jsonJSON 编解码Marshal, Unmarshal
io基础 I/OCopy, ReadFull, Writer
os操作系统接口Open, Create, Remove
time时间日期Now, Parse, Sleep
sync并发同步Mutex, WaitGroup, Once
context上下文管理Background, WithTimeout

标准库导入示例

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strings"
    "time"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // 字符串操作
    text := "Hello, Go!"
    fmt.Println(strings.ToLower(text))  // hello, go!
    fmt.Println(strings.Contains(text, "Go"))  // true

    // JSON 编解码
    user := User{Name: "Alice", Email: "alice@example.com"}
    data, _ := json.Marshal(user)
    fmt.Println(string(data))  // {"name":"Alice","email":"alice@example.com"}

    // HTTP 请求
    resp, _ := http.Get("https://example.com")
    defer resp.Body.Close()
    _ = resp

    // 时间处理
    fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
}

包速查表

操作命令
模块管理
初始化模块go mod init <module-name>
整理依赖go mod tidy
下载依赖go mod download
验证依赖go mod verify
vendor 模式go mod vendor
依赖操作
添加依赖go get <package>
添加特定版本go get <package>@version
仅添加不安装go get -d <package>
升级所有依赖go get -u ./...
查询操作
查看所有依赖go list -m all
查看模块信息go list -m <package>
查看可用版本go list -m -versions <package>
依赖关系图go mod graph
解释依赖来源go mod why <package>
编辑操作
添加 replacego mod edit -replace=old=new
删除 replacego mod edit -dropreplace=old
工作区
初始化工作区go work init
添加模块到工作区go work use <path>
同步工作区go work sync

练习

  1. 创建一个 math 包,实现加减乘除函数
  2. 使用 init 函数实现配置自动加载
  3. 编写示例代码展示你的包的用法

← 返回基础概念 | 函数与方法 | 继续:作用域 →