Go 包管理講解
大部分編程語言都有其代碼組織方式,以方便管理我們所開發的代碼,比如PHP
的命名空間
(namespace
),Java
的package
,JavaScript
的module
;
Go 語言也有自己的代碼組織方式:包 (package
)。
Go 語言包管理歷史
Go
語言包管理歷史主要有以下幾個階段:
-
GOPATH
時代:項目放在GOPATH
環境變量所配置的目錄下的src
目錄中。 -
vendor
與各種版本管理工具百花齊放 -
官方
Go module
一統江湖。
包的定義
什麼是包 (package)?
在 Go 語言中,包是一個或多個源碼文件的集合,源碼文件以.go
爲後綴,在源碼文件中,我們可以聲明常量、變量、函數、自定義類型與方法。
我們開發的程序 (項目或者類庫) 是由一個或多個包構成的。
程序、包、源碼文件之間的關係如下圖所示:
包的聲明
對於Go
初學者來說,直接學習Go module
這種包管理工具就可以了,爲了後面更好地講解,我們先用go mod init
命令初始化一個項目:
//創建項目目錄
mkdir demo
//進入目錄
cd demo
//初始化項目
go mod init github.com/my/demo
go.mod
執行了go mod init
命令後,這時候可以看到demo
目錄下有一個go.mod
的文件,其內容如下:
module github.com/my/demo
go 1.18
go.mod
文件主要記錄項目的module
路徑、項目依賴包列表 (當前未依賴其包,所以爲空) 和go
的版本信息。
go.sum
如果我們要導入外部包,可以執行go get
命令:
go get github.com/go-sql-driver/mysql
執行後,會生成go.sum
文件,這個文件記錄着當前項目每個依賴包的哈希值,在項目構建時,會計算依賴包的哈希值,並與 go.sum 中對應的哈希值比較,以防止依賴包被竄改。
執行go get
命令後,go.mod
文件也會新增了一條依賴記錄:
module github.com/my/demo
go 1.18
require github.com/go-sql-driver/mysql v1.7.0 // indirect
main 包
Go 語言程序的入口main
函數,該函數所在的包必須爲main
:
package main
func main(){
//...
}
包的聲明
包的聲明位於源文件的第一行的package
語句,後面跟着包名:
package 包名
除了main
包外,其他包的名稱必須與對應的目錄同名,同一個目錄下所有源碼文件包名必須相同:
user/user.go
package user
type User struct {
ID int
Name string
}
user/list.go
package user
func (u *User) List() []User {
return []User{{ID: 1, Name: "test"}}
}
包的導入與使用
包聲明之後,就可以在其他包中導入了,導入包名使用import
語句。
導入路徑
標準庫包的導入路徑一般就是標準庫的名稱:
import "fmt"
import "time"
import "net/http"
如果是我們自己聲明的包,其導入路徑爲 module 路徑加上包名,比如我們項目 module 路徑爲github.com/my/demo
,那麼要導入user
包,其路徑爲:
import "github.com/my/demo/user"
對於有多重目錄的包來說,比如 controller 目錄下有一個 order 包,那麼其路徑需要包含完整的目錄名:
import "github.com/my/demo/controller/order"
兩種導入方式
Go 的包有兩種導入方式,一種是標準導入,一種是匿名導入。
標準導入
普通導入在關鍵字 import 後跟着導入路徑即可,然後就可以通過包名調用包下的變量、函數、常量了:
package main
import "github.com/my/demo/user"
func main() {
userList := user.List()
fmt.Println(userList)
}
Go 支持在導入的時候爲包取一個別名 (alias),這樣可以避免因包名相同而無法導入的問題,比如下面我們聲明一個名稱爲time
的包:
package time
import "time"
func Now() int64 {
return time.Now().Unix()
}
之後在 main 包中,調用自定義的time
包,同時導入標準庫time
包,由於導入時包名相同,因此可以給其中一個包起一個別名來避免無法導入的問題:
package main
import t "github.com/my/demo/time"
import "time"
func main() {
fmt.Println(time.Now().Unix())
fmt.Println(t.Now())
}
如果別名是一個點號,那麼此時導入的所有變量、函數、常量都不需要通過包名來訪問了:
package main
import . "github.com/my/demo/time"
import "time"
func main() {
fmt.Println(time.Now().Unix())
fmt.Println(Now())
}
如果導入多個包,也可將多個導入寫在一個import
語句中,用小括號括起來:
package main
import (
"time"
t "github.com/my/demo/time"
)
func main() {
fmt.Println(time.Now().Unix())
fmt.Println(Now())
}
匿名導入
通過import
語句導入的包後必須使用,否則無法通過編譯。
但有時候我們導入包僅僅只是爲了導入時自動執行包中的init
函數而已,在後續代碼中並不會使用該包,此時可以通過在包路徑前面加上空白符_
將導入聲明爲匿名導入:
import (
"database/sql"
"time"
_ "github.com/go-sql-driver/mysql"
)
可見性
對於定義在包裏的變量、常量、函數等代碼,如果不想被其他包調用,則其首字母應該設置爲小寫,小寫字母開頭的函數、變量、常量爲可見性爲private
,而首字母大寫的話,可見性爲public
,在其他包中被調用:
package user
//private
func eat(){
//....
}
//public
func Say(){
//....
}
內部包
我們開發的庫可能會分享 (比如通過Github
或者Gitlab
) 給其他開發者使用,當我們改動到其他開發者依賴的函數或變量時,就會對他們造成影響,破壞到他們的代碼,因此在開發時,要對外暴露通用的,不經常改動的代碼,對於會變動的邏輯,可以放在internal
目錄中,Go 語言不允許導入internal
包。
比如下面的目錄中,internal 包中所有的代碼,只能在本項目中使用,其他開發者無法導入:
.
├── cmd
│ └── main.go
├── go.mod
├── go.sum
├── internal
│ └── util
│ └── util.go
├── time
│ └── time.go
└── user
├── list.go
└── user.go
小結
包是 Go 語言代碼的組織單位,通過包,我們可以管理我們的代碼,尤其是當開發大型項目時,可以通過包進行模塊劃分,另外,我們也可以封裝自己的包以供其他開發者使用,或者在自己的項目中導入其他開發者的包。
最後,總結一下在這篇文章中的要點:
-
Go 包管理的歷史
-
包的聲明,對 go.mod 和 go.sum 文件有清楚的認識。
-
包的導入,包括導入路徑,導入方式,可見性以及內部包。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/j6-U5rgjmvJgah9btRb6Xw