外观
Go Package Design
Phase 02 — Go Core 涵盖:
package design
1. 学习目标
完成本知识点后,你应该能够:
- 理解 Go 包(package)是编译和复用的基本单元
- 掌握导出规则:首字母大写 = 公开,小写 = 包内私有
- 组织合理的项目目录结构(cmd、internal、pkg)
- 使用
internal目录限制包的导入范围 - 避免循环依赖,设计清晰的包边界
- 为数字孪生后端项目规划可维护的模块结构
2. 为什么需要
当代码从单个 main.go 增长到数百行,全部堆在一个文件里会变得难以维护。Go 通过 package 强制模块化:每个目录一个包,通过导入路径组合。
合理的 package design 让:
- 业务逻辑与 HTTP 层分离(后续换 Gin 不影响 core)
- 测试可以单独测试
internal/service而不启动 HTTP 服务器 - 团队成员并行开发不同包,减少冲突
3. 核心概念
3.1 一个目录一个包
myproject/
├── go.mod
├── cmd/
│ └── server/
│ └── main.go // package main
└── internal/
└── device/
└── agv.go // package devicemain.go 中:import "myproject/internal/device"
3.2 导出规则
| 标识符首字母 | 可见性 |
|---|---|
| 大写 | 包外可访问(exported) |
| 小写 | 仅包内可访问(unexported) |
3.3 internal 目录
Go 编译器强制:internal/ 下的包只能被父目录及子目录导入,外部模块无法 import。用于保护实现细节。
3.4 常见布局
| 目录 | 用途 |
|---|---|
cmd/ | 可执行入口,每个子目录一个 main |
internal/ | 私有应用逻辑,外部不可 import |
pkg/ | 可选,可被外部项目复用的库代码 |
api/ | OpenAPI 定义、proto 文件等 |
3.5 避免循环依赖
包 A import B,B 不能 import A。解决:抽取公共 interface 到第三个包,或合并过小的包。
4. 基础语法
示例项目结构见 workspace/phase-02/package-design/。
internal/device/agv.go
go
package device
import "fmt"
// AGV 导出类型
type AGV struct {
ID string
speed float64 // 小写:包外不可直接访问
}
func NewAGV(id string, speed float64) *AGV {
return &AGV{ID: id, speed: speed}
}
func (a *AGV) Speed() float64 { return a.speed }
func (a *AGV) Report() string {
return fmt.Sprintf("%s @ %.1f m/s", a.ID, a.speed)
}cmd/server/main.go
go
package main
import (
"fmt"
"myproject/internal/device"
)
func main() {
agv := device.NewAGV("AGV-001", 1.2)
fmt.Println(agv.Report())
// agv.speed = 2.0 // ❌ 编译错误:speed 未导出
}go.mod
go
module myproject
go 1.225. 代码解析
go
type AGV struct {
ID string // 导出
speed float64 // 未导出
}通过导出规则封装内部状态。包外只能通过 NewAGV 和 Speed() 访问,类似 TS 的 private 字段 + getter。
go
func NewAGV(...) *AGV构造函数模式:Go 没有 constructor 关键字,用 NewXxx 函数初始化,可在此做校验。
go
internal/device/internal 路径下的包,模块外部无法 import,防止 API 消费者依赖内部实现。
6. JavaScript / TypeScript 对比
| 概念 | Go | JavaScript / TypeScript |
|---|---|---|
| 模块单元 | package(目录) | file(ES module) |
| 可见性 | 首字母大小写 | export / private |
| 私有实现 | unexported + internal | module scope |
| 路径 | module path(go.mod) | import path |
| 循环依赖 | 编译禁止 | 运行时可能出问题 |
| monorepo | 多 module 或多 package | workspace / packages |
关键差异:
- Go 可见性是包级别,不是文件级别;同包所有文件共享可见性
internal是编译器 enforced,TS 的 private 只是编译期检查- Go 一个目录不能有两个 package(测试
_test除外)
7. 常见错误
错误 1:package 名与目录名不一致
go
// 目录 device/,文件写 package dev // ❌ 惯例要求一致
package device // ✅错误 2:循环 import
handler → service → handler // ❌ 编译失败错误 3:过度拆分
每个 struct 一个 package 会导致 import 爆炸。按职责而非按类型拆分。
错误 4:滥用 pkg/
pkg/ 意味「给外部用的库」。应用私有代码放 internal/。
错误 5:main 包包含业务逻辑
go
// ❌ 全部写在 cmd/server/main.go
// ✅ main 只做组装:读配置、启动 server、依赖注入8. 实际应用
数字孪生后端推荐结构
twin-server/
├── go.mod
├── cmd/
│ └── api/
│ └── main.go # 启动 HTTP
├── internal/
│ ├── device/ # AGV、Sensor 领域模型
│ ├── service/ # 业务逻辑
│ ├── handler/ # HTTP handlers
│ └── store/ # 内存/DB 存储
└── api/
└── openapi.yaml # 可选 API 文档Phase 03 REST API 项目可从此结构起步,Phase 04 接入 PostgreSQL 时只需扩展 store/。
9. 深入理解
9.1 包初始化 init()
每个包可有 func init(),在 import 时自动执行。避免复杂逻辑和隐式副作用。
9.2 接口定义的位置
- 接口在使用方包定义(accept interfaces)
- 实现在提供方包(return structs)
9.3 命名
- package 名简短小写单数:
device不是devices - 避免 stutter:
device.DeviceOK,device.DeviceManager可考虑device.Manager
9.4 go mod 与 import path
import path = module名/相对路径。移动目录需同步更新 import。
10. 练习
详细题目见
exercises/phase-02-core/04-package-design.md。
Level 1 — 基础
练习 1.1:创建 internal/device 包,导出 AGV struct 和 NewAGV。
练习 1.2:在 main 中 import 并使用,验证小写字段不可访问。
练习 1.3:同包多文件:types.go + agv.go。
Level 2 — 应用
练习 2.1:添加 internal/sensor 包,实现 Sensor 类型。
练习 2.2:在 internal/registry 中管理 device 和 sensor。
练习 2.3:用 interface 在 registry 中统一存储 DeviceReporter。
Level 3 — 综合
练习 3.1:拆分 handler 层(仅打印)和 service 层(业务逻辑)。
练习 3.2:设计 store 包 interface + 内存实现。
Level 4 — 项目实践
练习 4.1:在 workspace/phase-02/package-design/ 搭建 mini twin-server 骨架:cmd + internal 三层结构,可 go run ./cmd/server。
11. 学习检查
- Go 如何控制符号的可见性?
internal目录的特殊规则是什么?- 为什么不能循环 import?如何解决?
cmd/和internal/通常放什么?- package 名与目录名为什么要一致?
- 面向 interface 编程时,interface 应定义在哪一侧?
12. 下一步
| 已完成 | 下一知识点 | 关系 |
|---|---|---|
| package design | goroutine / channel | 并发代码也需合理分包 |
| Phase 03 HTTP | handler 包是 Web 层的核心 |
建议进入并发专题,之后 Phase 03 会用 package design 组织 REST API 项目。