返回介绍

5.6 公开或未公开的标识符

发布于 2024-10-11 12:39:00 字数 9944 浏览 0 评论 0 收藏 0

要想设计出好的 API,需要使用某种规则来控制声明后的标识符的可见性。Go 语言支持从包里公开或者隐藏标识符。通过这个功能,让用户能按照自己的规则控制标识符的可见性。在第 3 章讨论包的时候,谈到了如何从一个包引入标识符到另一个包。有时候,你可能不希望公开包里的某个类型、函数或者方法这样的标识符。在这种情况,需要一种方法,将这些标识符声明为包外不可见,这时需要将这些标识符声明为未公开的。

让我们用一个示例程序来演示如何隐藏包里未公开的标识符,如代码清单 5-64 所示。

代码清单 5-64 listing64/

counters/counters.go
-----------------------------------------------------------------------
01 // counters 包提供告警计数器的功能
02 package counters
03
04 // alertCounter 是一个未公开的类型
05 // 这个类型用于保存告警计数
06 type alertCounter int

listing64.go
-----------------------------------------------------------------------
01 // 这个示例程序展示无法从另一个包里
02 // 访问未公开的标识符
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing64/counters"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13   // 创建一个未公开的类型的变量
14   // 并将其初始化为 10
15   counter := counters.alertCounter(10)
16
17   // ./listing64.go:15: 不能引用未公开的名字
18   //          counters.alertCounter
19   // ./listing64.go:15: 未定义:counters.alertCounter
20
21   fmt.Printf("Counter: %d\n", counter)
22 }

这个示例程序有两个代码文件。一个代码文件名字为 counters.go,保存在 counters 包里;另一个代码文件名字为 listing64.go,导入了 counters 包。让我们先从 counters 包里的代码开始,如代码清单 5-65 所示。

代码清单 5-65  counters /counters.go

01 // counters 包提供告警计数器的功能
02 package counters
03
04 // alertCounter 是一个未公开的类型
05 // 这个类型用于保存告警计数
06 type alertCounter int

代码清单 5-65 展示了只属于 counters 包的代码。你可能会首先注意到第 02 行。直到现在,之前所有的示例程序都使用了 package main ,而这里用到的是 package counters 。当要写的代码属于某个包时,好的实践是使用与代码所在文件夹一样的名字作为包名。所有的 Go 工具都会利用这个习惯,所以最好遵守这个好的实践。

counters 包里,我们在第 06 行声明了唯一一个名为 alertCounter 的标识符。这个标识符是一个使用 int 作为基础类型的类型。需要注意的是,这是一个未公开的标识符。

当一个标识符的名字以小写字母开头时,这个标识符就是未公开的,即包外的代码不可见。如果一个标识符以大写字母开头,这个标识符就是公开的,即被包外的代码可见。让我们看一下导入这个包的代码,如代码清单 5-66 所示。

代码清单 5-66 listing64.go

01 // 这个示例程序展示无法从另一个包里
02 // 访问未公开的标识符
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing64/counters"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13   // 创建一个未公开的类型的变量
14   // 并将其初始化为 10
15   counter := counters.alertCounter(10)
16
17   // ./listing64.go:15: 不能引用未公开的名字
18   //                       counters.alertCounter
19   // ./listing64.go:15: 未定义:counters.alertCounter
20
21   fmt.Printf("Counter: %d\n", counter)
22 }

代码清单 5-66 中的 listing64.go 的代码在第 03 行声明了 main 包,之后在第 08 行导入了 counters 包。在这之后,我们跳到 main 函数里的第 15 行,如代码清单 5-67 所示。

代码清单 5-67 listing64.go:第 13 到 19 行

13   // 创建一个未公开的类型的变量
14   // 并将其初始化为 10
15   counter := counters.alertCounter(10)
16
17   // ./listing64.go:15: 不能引用未公开的名字
18   //                       counters.alertCounter
19   // ./listing64.go:15: 未定义:counters.alertCounter

在代码清单 5-67 的第 15 行,代码试图创建未公开的 alertCounter 类型的值。不过这段代码会造成第 15 行展示的编译错误,这个编译错误表明第 15 行的代码无法引用 counters.alertCounter 这个未公开的标识符。这个标识符是未定义的。

由于 counters 包里的 alertCounter 类型是使用小写字母声明的,所以这个标识符是未公开的,无法被 listing64.go 的代码访问。如果我们把这个类型改为用大写字母开头,那么就不会产生编译器错误。让我们看一下新的示例程序,如代码清单 5-68 所示,这个程序在 counters 包里实现了工厂函数。

代码清单 5-68 listing68/

counters/counters.go
-----------------------------------------------------------------------
01 // counters 包提供告警计数器的功能
02 package counters
03
04 // alertCounter 是一个未公开的类型
05 // 这个类型用于保存告警计数
06 type alertCounter int
07
08 // New 创建并返回一个未公开的
09 // alertCounter 类型的值
10 func New(value int) alertCounter {
11   return alertCounter(value)
12 }

listing68.go
-----------------------------------------------------------------------
01 // 这个示例程序展示如何访问另一个包的未公开的
02 // 标识符的值
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing68/counters"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13   // 使用 counters 包公开的 New 函数来创建
14   // 一个未公开的类型的变量
15   counter := counters.New(10)
16
17   fmt.Printf("Counter: %d\n", counter)
18 }

这个例子已经修改为使用工厂函数来创建一个未公开的 alertCounter 类型的值。让我们先看一下 counters 包的代码,如代码清单 5-69 所示。

代码清单 5-69  counters /counters.go

01 // counters 包提供告警计数器的功能
02 package counters
03
04 // alertCounter 是一个未公开的类型
05 // 这个类型用于保存告警计数
06 type alertCounter int
07
08 // New 创建并返回一个未公开的
09 // alertCounter 类型的值
10 func New(value int) alertCounter {
11   return alertCounter(value)
12 }

代码清单 5-69 展示了我们对 counters 包的改动。 alertCounter 类型依旧是未公开的,不过现在在第 10 行增加了一个名为 New 的新函数。将工厂函数命名为 New 是 Go 语言的一个习惯。这个 New 函数做了些有意思的事情:它创建了一个未公开的类型的值,并将这个值返回给调用者。让我们看一下 listing68.go 的 main 函数,如代码清单 5-70 所示。

代码清单 5-70 listing68.go

11 // main 是应用程序的入口
12 func main() {
13   // 使用 counters 包公开的 New 函数来创建
14   // 一个未公开的类型的变量
15   counter := counters.New(10)
16
17   fmt.Printf("Counter: %d\n", counter)
18 }

在代码清单 5-70 的第 15 行,可以看到对 counters 包里 New 函数的调用。这个 New 函数返回的值被赋给一个名为 counter 的变量。这个程序可以编译并且运行,但为什么呢? New 函数返回的是一个未公开的 alertCounter 类型的值,而 main 函数能够接受这个值并创建一个未公开的类型的变量。

要让这个行为可行,需要两个理由。第一,公开或者未公开的标识符,不是一个值。第二,短变量声明操作符,有能力捕获引用的类型,并创建一个未公开的类型的变量。永远不能显式创建一个未公开的类型的变量,不过短变量声明操作符可以这么做。

让我们看一个新例子,这个例子展示了这些可见的规则是如何影响到结构里的字段,如代码清单 5-71 所示。

代码清单 5-71 listing71/

entities/entities.go
-----------------------------------------------------------------------
01 // entities 包包含系统中
02 // 与人有关的类型
03 package entities
04
05 // User 在程序里定义一个用户类型
06 type User struct {
07   Name string
08   email string
09 }

listing71.go
-----------------------------------------------------------------------
01 // 这个示例程序展示公开的结构类型中未公开的字段
02 // 无法直接访问
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing71/entities"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13   // 创建 entities 包中的 User 类型的值
14   u := entities.User{
15     Name: "Bill",
16     email: "bill@email.com",
17   }
18
19   // ./example69.go:16: 结构字面量中结构 entities.User
20   //          的字段’email’未知
21
22   fmt.Printf("User: %v\n", u)
23 }

代码清单 5-71 中的代码有一些微妙的变化。现在我们有一个名为 entities 的包,声明了名为 User 的结构类型,如代码清单 5-72 所示。

代码清单 5-72  entities /entities.go

01 // entities 包包含系统中
02 // 与人有关的类型
03 package entities
04
05 // User 在程序里定义一个用户类型
06 type User struct {
07   Name string
08   email string
09 }

代码清单 5-72 的第 06 行中的 User 类型被声明为公开的类型。 User 类型里声明了两个字段,一个名为 Name 的公开的字段,一个名为 email 的未公开的字段。让我们看一下 listing71.go 的代码,如代码清单 5-73 所示。

代码清单 5-73 listing71.go

01 // 这个示例程序展示公开的结构类型中未公开的字段
02 // 无法直接访问
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing71/entities"
09 )
10
11 // main 是程序的入口
12 func main() {
13   // 创建 entities 包中的 User 类型的值
14   u := entities.User{
15     Name: "Bill",
16     email: "bill@email.com",
17   }
18
19   // ./example69.go:16: 结构字面量中结构 entities.User
20   //          的字段'email'未知
21
22   fmt.Printf("User: %v\n", u)
23 }

代码清单 5-73 的第 08 行导入了 entities 包。在第 14 行声明了 entities 包中的公开的类型 User 的名为 u 的变量,并对该字段做了初始化。不过这里有一个问题。第 16 行的代码试图初始化未公开的字段 email ,所以编译器抱怨这是个未知的字段。因为 email 这个标识符未公开,所以它不能在 entities 包外被访问。

让我们看最后一个例子,这个例子展示了公开和未公开的内嵌类型是如何工作的,如代码清单 5-74 所示。

代码清单 5-74 listing74/

entities/entities.go
-----------------------------------------------------------------------
01 // entities 包包含系统中
02 // 与人有关的类型
03 package entities
04
05 // user 在程序里定义一个用户类型
06 type user struct {
07   Name string
08   Email string
09 }
10
11 // Admin 在程序里定义了管理员
12 type Admin struct {
13   user  // 嵌入的类型是未公开的
14   Rights int
15 }


listing74.go
-----------------------------------------------------------------------
01 // 这个示例程序展示公开的结构类型中如何访问
02 // 未公开的内嵌类型的例子
03 package main
04
05 import (
06   "fmt"
07
08   "github.com/goinaction/code/chapter5/listing74/entities"
09 )
10
11 // main 是应用程序的入口
12 func main() {
13   // 创建 entities 包中的 Admin 类型的值
14   a := entities.Admin{
15     Rights: 10,
16   }
17
18   // 设置未公开的内部类型的
19   // 公开字段的值
20   a.Name = "Bill"
21   a.Email = "bill@email.com"
22
23   fmt.Printf("User: %v\n", a)
24 }

现在,在代码清单 5-74 里, entities 包包含两个结构类型,如代码清单 5-75 所示。

代码清单 5-75  entities /entities.go

01 // entities 包包含系统中
02 // 与人有关的类型
03 package entities
04
05 // user 在程序里定义一个用户类型
06 type user struct {
07   Name string
08   Email string
09 }
10
11 // Admin 在程序里定义了管理员
12 type Admin struct {
13   user  // 嵌入的类型未公开
14   Rights int
15 }

在代码清单 5-75 的第 06 行,声明了一个未公开的结构类型 user 。这个类型包括两个公开的字段 NameEmail 。在第 12 行,声明了一个公开的结构类型 AdminAdmin 有一个名为 Rights 的公开的字段,而且嵌入一个未公开的 user 类型。让我们看一下 listing74.go 的 main 函数,如代码清单 5-76 所示。

代码清单 5-76 listing74.go:第 11 到 24 行

11 // main 是应用程序的入口
12 func main() {
13   // 创建 entities 包中的 Admin 类型的值
14   a := entities.Admin{
15     Rights: 10,
16   }
17
18   // 设置未公开的内部类型的
19   // 公开字段的值
20   a.Name = "Bill"
21   a.Email = "bill@email.com"
22
23   fmt.Printf("User: %v\n", a)
24 }

让我们从代码清单 5-76 的第 14 行的 main 函数开始。这个函数创建了 entities 包中的 Admin 类型的值。由于内部类型 user 是未公开的,这段代码无法直接通过结构字面量的方式初始化该内部类型。不过,即便内部类型是未公开的,内部类型里声明的字段依旧是公开的。既然内部类型的标识符提升到了外部类型,这些公开的字段也可以通过外部类型的字段的值来访问。

因此,在第 20 行和第 21 行,来自未公开的内部类型的字段 NameEmail 可以通过外部类型的变量 a 被访问并被初始化。因为 user 类型是未公开的,所以这里没有直接访问内部类型。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文