Golang 包的定义、引入和工程管理

代码结构的编排是一门编程语言的基础,本文专门谈谈 Golang 包的定义、引入和工程管理,搞清楚 Golang 各个包之间是如何定义和引入的。

包的基本概念

在实际开发中,随着代码量越来越大功能越来越复杂,单个文件已经不适应继续使用的时候就需要对代码进行切割。

将各个功能切割最常用的是通过函数,那么对单个文件中很多的函数切割就用到包的概念。

一句话定义:包是用来组织源代码,并实现命名空间的管理。

用到包的编程语言有很多,Python 中的包就和 Go 的很相似,都是通过 import 来引入,C 也有类似的 #include 等方式来定义命名空间。

既然包是用来组织源代码的,那么就需要有定义和引用。

包定义

包的定义很简单,只需要在源码文件中第一行有效代码中加上:package packageName 即可。

虽然很简单,但是有些规则是需要知道的。

  • 必须定义一个名为 main 的包且包中有 main 函数。
  • 每级的同级目录下只允许定义一个包。
  • 包名命名规范都是小写,使用简短命名方式(不强制)。
  • 包名和所在的目录同名(不强制)。

包引用

引用方式

单行引入:

import "fmt"
import "sync" 

多行引入:

import(
    "fmt"
    "sync"
)
匿名引用

Go 语言在设计之处为了语言的简洁性,在引入没有使用到的包会产生编译错误,如果想要先引入,之后再使用可以使用 _ 的方式引用。

import _ "fmt"
别名使用

在有些时候我们需要对引入的包进行重新命名,比如 Python 使用 as 进行别名定义,在 Go 语言中只需要在包名之前定义别名即可。

import f "fmt"
点操作符

包的使用方式是:包名+方法名。如果程序内部里频繁使用了一个工具包每次都使用包名会很繁琐,所以就通过 . 省略包名(类似 import *)。

import . "fmt"

func main() {
    Println("hello, world")
}
包初始化

每个包都允许有一个 init 函数,当这个包被导入时会立即执行 init 函数。init 函数可以很方便的做一些初始化的任务。

  • init 函数优先于 main 函数执行。
  • 在一个引用链中,包的初始化是深度优先的(例如:main -> A -> B -> C,初始化顺序为:C.init -> B.init -> A.init -> main)。

包的工程管理

因为 Go 语言是发展极快的语言,它本身也在不断的优化和进化,许多之前的老版本版本引入包的方式和现在已经有很大的区别了。由于这里历史包袱,导致新手在学习这节知识的时候很懵逼,很乱,不知道从哪里开始。这里用目前最新的版本:v1.17.2 来讲解。

首先从 Golang 的环境配置说起,安装好之后,通过命令行来看下当前 Golang 的环境配置。

go env

有两个个比较重要的环境变量:

  • GOROOT:Golang 安装目录的路径,默认编译器程序和系统包,也可以放置三方包。
  • GOPATH:当前工作目录,放置编译后二进制和 import 包时的搜索路径,一般有三个目录: bin、pkg、src。

不过这些我们都不用,因为这些都是很久之间版本的手动引入包和依赖管理,你大概知道有这么一个东西就够了,装好之后默认环境变量配置就可以了。

我们要使用更加强大的 go modules 来进行包的自动管理。

Modules

go modules 是 golang 1.11 就新加的特性,现在已经很成熟了。

Modules 官方定义为:

模块是相关 Go 包的集合。modules 是源代码交换和版本控制的单元。
go 命令直接支持使用 modules ,包括记录和解析对其他模块的依赖性。
modules 替换旧的基于 GOPATH 的方法来指定在给定构建中使用哪些源文件。

配置环境变量

Modules 需要 Golang 1.11 以上的版本才可以使用,我们先查看一下自己本地的 Go 版本是多少:

$ go version
go version go1.17.2 darwin/amd64

接下来我们需要设置一下引入包的方式,这个参数是:GO111MODULE,可以通过 go env 查看 GO111MODULE 的值。

GO111MODULE 有三个值:

  • off:不支持 module 功能,寻找依赖包的方式将会沿用旧版本 GOPATH 模式或者 vendor 目录模式。
  • on:支持 modules 功能,不会使用旧版本 GOPATH 模式或者 vendor 目录模式。
  • auto:默认值,会根据当前目录来决定是否启用 module 功能。

为了方便使用,这里统一将 GO111MODULE 的值设置为 on

go mod

Module 功能通过 go mod 命令来管理包。

go mod 有以下命令:

Usage:

	go mod <command> [arguments]

The commands are:

	download    download modules to local cache
	edit        edit go.mod from tools or scripts
	graph       print module requirement graph
	init        initialize new module in current directory
	tidy        add missing and remove unused modules
	vendor      make vendored copy of dependencies
	verify      verify dependencies have expected content
	why         explain why packages or modules are needed
项目实战

在 GOPATH 目录之外新建一个目录,并使用 go mod init 进行初始化。

MacBook:Downloads zhangyi$ mkdir hello
MacBook:Downloads zhangyi$ cd hello

MacBook:hello zhangyi$ ll
total 0
drwxr-xr-x   2 zhangyi  staff    64 10 27 10:05 ./
drwx------@ 34 zhangyi  staff  1088 10 27 10:05 ../

MacBook:hello zhangyi$ go mod init hello
go: creating new go.mod: module hello

MacBook:hello zhangyi$ ll
total 8
drwxr-xr-x   3 zhangyi  staff    96 10 27 10:05 ./
drwx------@ 34 zhangyi  staff  1088 10 27 10:05 ../
-rw-r--r--   1 zhangyi  staff    22 10 27 10:05 go.mod

初始化完成后会生成一个名为 go.mod 的文件,这个就是 Models 的版本管理文件,里面记录了当前自定义命名的包和 Golang 的版本。

MacBook:hello zhangyi$ cat go.mod 
module hello

go 1.17
MacBook:hello zhangyi$ 

然后在本目录下新建 Go 源码文件。

MacBook:hello zhangyi$ touch main.go
MacBook:hello zhangyi$ vi main.go 

接下来就是在文件中开始写代码:

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

启动运行:

MacBook:hello zhangyi$ go run main.go 
main.go:3:8: no required module provides package github.com/gin-gonic/gin; to add it:
	go get github.com/gin-gonic/gin

Models 已经将整个项目的包进行关联,不需要关心程序目录和工作目录,也不用关系导入是绝对路径还是相对路径,完全是通过 Models 进行自动管理。

这里仅提示没有安装 Gin 这个包:

go get github.com/gin-gonic/gin

运行结果:

MacBook:hello zhangyi$ go get github.com/gin-gonic/gin

go get: added github.com/gin-contrib/sse v0.1.0
go get: added github.com/gin-gonic/gin v1.7.4
go get: added github.com/go-playground/locales v0.13.0
go get: added github.com/go-playground/universal-translator v0.17.0
go get: added github.com/go-playground/validator/v10 v10.4.1
go get: added github.com/golang/protobuf v1.3.3
go get: added github.com/json-iterator/go v1.1.9
go get: added github.com/leodido/go-urn v1.2.0
go get: added github.com/mattn/go-isatty v0.0.12
go get: added github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go get: added github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
go get: added github.com/ugorji/go/codec v1.1.7
go get: added golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
go get: added golang.org/x/sys v0.0.0-20200116001909-b77594299b42
go get: added gopkg.in/yaml.v2 v2.2.8
MacBook:hello zhangyi$ 
MacBook:hello zhangyi$ go run main.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

浏览器访问 http://localhost:8080/ping 就可以看到结果了。

go get 升级

Golang 包的版本号管理一般都是:主版本、次要版本、修订版本(x.y.z)这种方式。

使用 go get 进行升级有如下规则:

  • go get -u 将会升级到最新的次要版本或者修订版本。
  • go get -u=patch 将会升级到最新的修订版本。
  • go get package@version 将会升级到指定的版本号。
  • go get 如果有版本的更改,那么 go.mod 文件也会更改。
自定义包

我们已经可以引入 Go 语言默认的包和通过 go get 来引入第三方包,那该如何引入自定义的包呢?

按照上面的方法,我们在新建一个 demo 的目录,然后使用 go mod init app初始化,最后新建 main.godemo 目录:

MacBook:demo zhangyi$ tree .
.
├── demo
│   ├── demo1.go
│   └── demo2.go
├── go.mod
└── main.go

1 directory, 4 files

main.go

package main

import "fmt"
import "app/demo"


func main() {
	demo.Demo1()
	fmt.Println("Hello, world.")
	demo.Demo2()
}

demo/demo1.go

package demo

import "fmt"


func Demo1() {
	fmt.Println("Demo-1...")
}

demo/demo2.go

package demo

import "fmt"


func Demo2() {
	fmt.Println("Demo-2...")
}

运行结果:

MacBook:demo zhangyi$ go run main.go 
Demo-1...
Hello, world.
Demo-2...
MacBook:demo zhangyi$ 

最后需要强调的一点是,导入包时其实是导入目录,导入目录后,可以使用这个目录下的所有包,按照惯例包名和目录名通常会设置成一样,所以会让你有一种你导入的是包的错觉。当然你也可以不把目录和包名设置成一样的,导入的时候是导入目录,然后直接使用 包名.方法 即可。

参考文章:
https://juejin.cn/post/6844904167073382408
https://juejin.cn/post/6844903798658301960

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值