go-book/src/project-structure/package.md
2023-09-08 06:12:08 +08:00

6.8 KiB
Raw Blame History

使用包来组织代码

Go 是通过包这个概念来组织项目中分属不同功能的代码的。其实在之前的最小示例中已经见识过了定义包的语句。在 Go 程序中,包是使用package关键词来定义的。

但是包的名称不是可以随意起的Go 在包的命名上有自己的一套规范。

  1. 承载主启动函数的文件,其包名为main
  2. 同一个目录中的文件,包名的定义必须相同。
  3. 除应用根目录以外,其他目录中的包名应该与目录名相同。例如目录名为entities,那么这个目录中的文件就需要使用package entities来声明其包名。
一般习惯上把项目根目录命名为`main`包。而根目录通常也是放置主启动文件的地方,不建议把项目的主启动文件放置到其他目录中。项目的根目录中一般除了项目的主启动文件以外,一般也不建议再放置其他的功能文件,或者最多放置一些与项目启动相关的功能。

引用项目中定义的包

在一个代码文件中使用项目另一个包中定义的内容,就需要先引入这个包。在 Go 中,引用包是通过import关键字完成的。

其实import语句在之前的最小实例中也已经见到过了,在那个示例中是用来引入标准库中的fmt包的。这也是import关键字最简单的使用方法——import "被引入包名"

在引入 Go 标准库中的包时,是可以直接书写包的名称的,比如import "fmt"import "io"import "os"。但是在引入项目中自定义的包或者是其他依赖库中的包时,就需要书写包的完整路径。例如import "archgrid.xyz/ebusiness/config"import "github.com/samber/lo"

在一个代码文件中通常不可能只会引入一个包而是会同时引入若干个包这是一个非常普遍的事情。与其他的语言不一样Go 不需要每一书写一个import来完成多个包的引用。例如以下这种引入方式实际上是不必要的。

import "fmt"
import "os"

import "archgrid.xyz/ebusiness/config"
import "archgrid.xyz/ebusiness/global"
import "github.com/samber/lo"

事实上,如果真的在代码中写出了这样的引入语句,也会被go fmt命令自动优化掉。在 Go 代码文件中,这样引入若干个包的语句通常会被简化为一条使用括号包裹的import语句。也就是以下这样。

import (
    "fmt"
    "os"

    "archgrid.xyz/ebusiness/config"
    "archgrid.xyz/ebusiness/global"
    "github.com/samber/lo"
)
在Go语言中这种使用括号来精简批量语句是一种使用非常普遍的语法范式在后面会介绍到还有哪些关键字可以使用这种语法范式。

引入的包在代码中的使用

一个包在使用import关键字引入之后就可以在当前代码文件中使用了,但是使用的时候需要记住的是,引入的内容需要通过包名来访问。

那这就有一个需要解决的问题了,一个被引入的包的包名是什么?

根据 Go 语言的语法规定,一个被引入的包,它的包名是它完整路径的最后一个部分。例如"github.com/samber/lo"的包名是lo"archgrid.xyz/ebusiness/config"的包名是config"fmt"的包名就还是fmt。此时根据包名就可以访问所引入包中的内容,例如可以使用config.Service来访问archgrid.xyz/ebusiness/config中声明的Service变量。

如果一个代码文件中引入的两个包的最后一个部分重名,那么就只能使用 Go 提供的给包重命名的方法来去分它们了。

如果放任引入的包发生重名的问题,不仅会使程序无法辨识所使用的包成员,还会因此导致程序无法通过编译。

以下是一个对引入的重名包进行重命名的示例。

import (
    "io"

    smithyIO "github.com/aws/smithy-go/io"
)

在这个示例中使用了一个新的语法:在被引入的包路径前面增加了一个标识符。这个新增加的标识符就是在当前文档中给这个被引入的包的命名。这样一来,在当前的文档中,在访问这个包的内容时,就不能像之前一样使用包路径的最后一个部分来做包名称了,而是要使用这个新增加的标识符。例如之前的用法io.NewRingBuffer(buf),现在就要变成smithyIO.NewRingBuffer(buf)了。

对一个包进行重命名并不局限在包发生了重名的现象时,其实在任何时刻都可以对包进行重命名,条件只有一个——保证包在当前文件中的名称是唯一的。

几个特殊的用法

_在 Go 中做为一个特殊的标识符,大量的使用在程序的各个角落,这里来说它在引入包的时候的用法。在 Go 中,_通常的意义表示忽略,所以在引入包的情况下也是一样的。使用_来标识被引入的一个包,实际上并不会引入包内的任何内容,只会执行其中的init()函数完成包的初始化。这种操作在引入数据库驱动或者是一些框架的插件系统时非常有用,可以避免许多不必要的内容混入当前的代码文档中。

关于`init()`函数将在后面讲述函数的章节进行更加详细的说明。

_相反,使用.对被引入的包进行标识,可以将被引入的包中的内容完全引入当前的代码文档中,使之可以不通过包名即可使用,就像是这些内容就是在当前包中定义的一样。

使用`.`标识被引入包的用法一般并不推荐,通常认为这种用法比较容易引起命名空间的混乱。

如何引入一个依赖库中的子级包

我们对于大部分常用依赖库中提供功能的了解都是通过集中存放 Go 依赖库文档的网站Go PKG的。我们在浏览很多依赖库的时候都会发现它们在组织自身的代码的时候,也使用了大量的包。这就要求我们在使用的时候必须去引用其中的子级包。

比方说常用的github.com/fufuok/utils工具库,除了直接在utils包中提供的工具函数,还有大量的工具函数是在其子级包中提供的,例如提供加密算法支持的名为xcrypto子级包。这种子级包在使用的时候依旧需要遵照前面提到的原则,使用被引入包的绝对路径来引入,所以xcrypto包的引入语法就是import github.com/fufuok/utils/xcrypto

虽然在使用子级包之前已经通过`go get`安装了父级包的依赖,但是在使用子级包的时候,同样需要重新用`go get`安装一下子级包,虽然这并不会修改`go.mod`文件。不过目前市面上的大部分IDE都能够提示和处理这种情况。

一个依赖库中提供的子包可以在[Go PKG](https://pkg.go.dev)中依赖库文档的`Directories`部分中找到。