11. 包和模块

在第 9 章中介绍结构体及其内部的字段的可见性时介绍到 Go 语言中包的概念。前面章节的例子,绝大部分都只是一个 main.go 文件就可以了,这对于一个简单的程序已经够了。但是,随着项目越来越大,代码也会越来越多,所有代码都放在一个目录、一个包中,会出现很多问题,如文件越来越多,难以维护;不同功能的代码混杂在一起,可读性差等等。为了解决这些问题,Go 引入了两个十分重要的概念:

  • Package(包):用于组织代码。
  • Module(模块):用于组织项目和管理依赖。

二者虽然都可以理解为“组织代码”,但职责完全不同。可以简单理解为:Package 是代码组织单位,而 Module 是项目管理单位。本章将详细介绍二者的设计思想、使用方法以及它们之间的关系。

11.1. Package

11.1.1. 什么是 Package

Package(包)是 Go 中最基本的代码组织单位。可以把一个 Package 理解成一组完成同一类功能的 Go 源文件,如前面章节中我们大量使用的 Go 标准库中的 fmt 包,该包的主要功能就是格式化输入输出,每个 Package 都负责一类功能。除了 fmt 包,在 Go 标准库中还有大量其他的包,如:

  • strings:字符串处理
  • math:数学计算
  • os:操作系统相关

这些标准库中的包,我们在使用的时候使用 import 导入,代码如下:

import "fmt"

当有多个包需要导入时,可以用如下的方式:

import (
    "fmt"
    "strings"
)

11.1.2. 构建自己的代码

我们还是构建一个 animal 的项目,目录结构如下:

├── animal
│   ├── cat
│   │   ├── cat.go
│   ├── dog
│   │   ├── dog.go
├── main.go

在 Go 语言中,一个文件夹就是一个 package,因此在 cat.go 文件中写下如下代码:

package cat

import "fmt"

func Meow() {
	fmt.Println("Cat Meow")
}

dog.go 文件中写下如下代码:

package dog

import "fmt"

func Bark() {
	fmt.Println("Dog Bark")
}

这样就构建好了两个文件,现在在 main.go 文件中引用这两个文件,代码如下:

package main

import (
	"animal/cat/cat"
	"animal/dog/dog"
)

func main() {
	dog.Bark()

	cat.Meow()
}

此处的 import 我们暂且按照相对路径导入。以 "animal/cat/cat" 为例,其中,animal 为目录,第一个 cat 也是文件夹名,最后的 cat 为 package 名。

此时,我们运行 go run main.go 发现报错:“package animal/cat is not in std (/usr/local/go/src/animal/cat) ”和“package animal/dog is not in std (/usr/local/go/src/animal/dog)”。这说明 Go 没有找到 animal/catanimal/dog 这两个包,而是把它们当成了标准库(stdlib)来查找。这个时候就需要使用到 Modules 的概念。

11.2. Module

Package 解决的是代码组织的问题。那么,一个项目越来越大之后,还会遇到新的问题,如如何管理第三方库;如何记录依赖版本等等。于是 Go 引入了 Module。Module 可以理解成一个完整的 Go 项目。

每个 Module 的根目录通常都会存在一个 go.mod 文件。对于一个新的项目,可以在当前空的项目文件夹下用如下的命令创建项目:

go mod init xxx

这样就可以创建一个新的项目,注意,这里的 xxx 称为模块路径,同时在项目下出现文件 go.mod,文件内容如下:

module xxx

go 1.26.1

对于我们上述的项目,在根目录下新建 go.mod 文件,并设置内容为:

module animal

go 1.26.1

此时,相当于模块路径为 animal,我们修改 main.go 文件中的 import 语法,如下:

package main

import (
	"animal/animal/cat"
	"animal/animal/dog"
)

func main() {
	dog.Bark()

	cat.Meow()
}

此时,再运行代码发现程序已经能正常运行了。我们发现在原先的相对路径前增加了模块名称,这也就对应着 import 中的语法,具体为:import 路径 = module 名称 + package 所在目录。

11.3. 本章小结

随着项目越来越大,代码也会越来越多,所有代码都放在一个目录、一个包中,会出现很多问题,为此,在 Go 语言中引入了 Package 和 Module 这两个概念,其中,Package(包)是 Go 中最基本的代码组织单位,主要解决的是代码组织的问题。而 Module 主要解决的是项目的管理问题,如管理第三方库,记录依赖版本等等,Package 和 Module 这两个概念称为开发大型项目的标配。