现在来看一下如何组织我们的代码。

为了组织复杂的库和系统代码,我们需要学习关于包的知识。在 Go 语言中,包名遵循 Go 项目的目录结构。如果我们建立一个购物系统,我们可能以 “shopping” 包名作为一个开始,然后把所有源代码文件放到 $GOPATH/src/shopping/ 目录中。

我们不会去想把所有东西都放在这个文件夹中。例如,我们可能想单独把数据库逻辑放在它自己的目录中。为了实现这个,我们创建一个子目录 $GOPATH/src/shopping/db 。子目录中文件的包名就是 db,但是为了从另一个包访问它,包括 shopping 包,我们需要导入 shopping/db

换句话说,当你想去命名一个包的时候,可以通过 package 关键字,提供一个值,而不是完整的层次结构(例如:「shopping」或者 「db」)。当你想去导入一个包的时候,你需要指定完整路径。

接下来,我们去尝试下。在你的 Go 的工作目录 src 文件夹下(我们已经在 基础 那一章节中介绍了),创建一个新的文件夹叫做 shopping ,然后在 shopping 文件夹下创建一个 db 文件夹。

shopping/db 文件夹下,创建一个叫做 db.go 的文件,然后在 db.go 文件中添加如下的代码:

package db

type Item struct {
  Price float64
}

func LoadItem(id int) *Item {
  return &Item{
    Price: 9.001,
  }
}

需要注意包名和文件夹名是一样的。而且很明显我们实际并没有连接数据库。这里使用这个例子只是为了展示如何组织代码。

现在,在主目录 shopping 下创建一个叫 pricecheck.go 的文件。它的内容是:

package shopping

import (
  "shopping/db"
)

func PriceCheck(itemId int) (float64, bool) {
  item := db.LoadItem(itemId)
  if item == nil {
    return 0, false
  }
  return item.Price, true
}

很有可能认为导入 shopping/db 有点特别,因为我们已经在 shopping 包/目录中。实际上,我们正在导入 $GOPATH/src/shopping/db,这意味着只要你在你的工作区间 src/test 目录中有一个名为 db 的包,你就可以轻松导入它。

你正在构建一个包,除了我们看到的你不再需要任何东西。为了构建一个可执行程序,你仍然需要 main 包。我比较喜欢的方式是在 shopping 目录下创建一个 main 子目录,然后再创建一个叫 main.go 的文件,下面是它的内容:

package main

import (
  "shopping"
  "fmt"
)

func main() {
  fmt.Println(shopping.PriceCheck(4343))
}

现在,你可以进入你的 shopping 项目运行代码,输入:

go run main/main.go

循环导入

当你编写更复杂的系统的时,你必然会遇到循环导入。例如,当 A 包导入 B 包,B 包又导入 A 包(间接或者直接导入)。这是编译器不能允许的。

让我们改变我们的 shopping 结构以复现这个错误。

Item 定义从 shopping/db/db.go 移到 shopping/pricecheck.go。你的 pricecheck.go 文件像下面这样:

package shopping

import (
  "shopping/db"
)

type Item struct {
  Price float64
}

func PriceCheck(itemId int) (float64, bool) {
  item := db.LoadItem(itemId)
  if item == nil {
    return 0, false
  }
  return item.Price, true
}

如果你尝试运行代码,你会从 db/db.go 得到两个关于 Item 未定义的错误。这看起来是说 Item 不存在 db 包中。它已经被移动到 shopping 包中,我们需要将 shopping/db/db.go 改变成:

package db

import (
  "shopping"
)

func LoadItem(id int) *shopping.Item {
  return &shopping.Item{
    Price: 9.001,
  }
}

现在但你尝试运行代码的时候,你将会得到 不允许循环导入 的错误。我们可以通过引入另一个包含共享结构体的包来解决这个问题。你的目录现在看起来像这个样子:

$GOPATH/src
  - shopping
    pricecheck.go
    - db
      db.go
    - models
      item.go
    - main
      main.go

pricecheck.go 将仍然导入 shopping/db,但是 db.go 现在导入 shopping/models 而不是 shopping,因此打破了循环。因为我们将共享的 Item 结构体移动到 shopping/models/item.go,我们现在需要去改变 shopping/db/db.gomodels 包中引用 Item 结构体。

package db

import (
  "shopping/models"
)

func LoadItem(id int) *models.Item {
  return &models.Item{
    Price: 9.001,
  }
}

你经常需要共享某些代码,不止 models,所以你可能有其他类似叫做 utilities 的目录,这些共享包的重要原则是它们不从 shopping 包或者任何子包中导入任何东西。在后面的章节中,我们将介绍可以帮助我们解决这些类型依赖关系的接口。

可见性

Go 用了一个简单的规则去定义什么类型和函数可以包外可见。如果类型或者函数名称以一个大写字母开始,它就具有了包外可见性。如果以一个小写字母开始,它就不可以。

这也可以应用到结构体字段。如果一个字段名以一个小写字母开始,只有包内的代码可以访问它们。

例如,我们的 items.go 文件中有个这样的函数:

func NewItem() *Item {
  // ...
}

它可以通过 models.NewItem() 这样被调用。但是如果函数命名为 newItem,我们将不能从不同的包访问它了。

去试试更改 shopping 代码中的函数,类型以及字段的名称。例如,如果你将 ItemPrice 字段命名为 price,你应该会获得一个错误。

包管理

我们用来 buildrungo 命令有一个 get 子命令,用于获取第三方库。go get 支持除了这个例子中的各种协议,我们可以从 Github 中获取一个库,意味着,你需要在你的电脑中安装 git

假设你已经安装了 Git,在 shell 中输入命令:

go get github.com/mattn/go-sqlite3

go get 获取远端的文件并把它们存储在你的工作区间中。去看看你的 $GOPATH/src 目录,你会发现除了我们创建的 shopping 项目之外,还有一个 github.com 目录,在里面,你会看到一个包含了 go-sqlite3 目录的 mattn 目录。

我们刚才只是讨论了如何导入我们工作区间的包。为了导入新安装的 go-sqlite3 包,我们要这样导入:

import (
  "github.com/mattn/go-sqlite3"
)

我知道这看起来像一个 URL,实际上,它只是希望导入在 $GOPATH/src/github.com/mattn/go-sqlite3 找到的 go-sqlite3 包。

依赖管理

go get 还有一些其他的技巧。如果我们在一个项目内使用 go get,它将浏览所有文件,查找 imports 的第三方库然后下载他们。某种程度上,我们的源代码变成了 Gemfile 或者 package.json

如果你调用 go get -u ,它将更新所有包(或者你可以通过 go get -u FULL_PACKAGE_NAME 更新一个具体的包)。

最后,你可能发现了 go get 的不足。一方面,这儿没有办法指定一个版本。他总是指向 master/head/trunk/default。这是一个较大的问题如果你有两个项目需要同一个库的不同版本。

为了解决这个问题,你可以使用一个第三方的依赖管理工具。他们仍然很年轻,但 goopgodep 是可信的。更多完整的列表在 go-wiki

热门教程

最新教程