返回介绍

5.5 嵌入类型

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

Go 语言允许用户扩展或者修改已有类型的行为。这个功能对代码复用很重要,在修改已有类型以符合新类型的时候也很重要。这个功能是通过 嵌入类型 (type embedding)完成的。嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。

通过嵌入类型,与内部类型相关的标识符会提升到外部类型上。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分。这样外部类型就组合了内部类型包含的所有属性,并且可以添加新的字段和方法。外部类型也可以通过声明与内部类型标识符同名的标识符来覆盖内部标识符的字段或者方法。这就是扩展或者修改已有类型的方法。

让我们通过一个示例程序来演示嵌入类型的基本用法,如代码清单 5-50 所示。

代码清单 5-50 listing50.go

01 // 这个示例程序展示如何将一个类型嵌入另一个类型,以及
02 // 内部类型和外部类型之间的关系
03 package main
04
05 import (
06   "fmt"
07 )
08
09 // user 在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }
14
15 // notify 实现了一个可以通过 user 类型值的指针
16 // 调用的方法
17 func (u *user) notify() {
18   fmt.Printf("Sending user email to %s<%s>\n",
19   u.name,
20   u.email)
21 }
22
23 // admin 代表一个拥有权限的管理员用户
24 type admin struct {
25   user // 嵌入类型
26   level string
27 }
28
29 // main 是应用程序的入口
30 func main() {
31   // 创建一个 admin 用户
32   ad := admin{
33     user: user{
34       name: "john smith",
35       email: "john@yahoo.com",
36     },
37     level: "super",
38   }
39
40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()
42
43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

在代码清单 5-50 中,我们的程序演示了如何嵌入一个类型,并访问嵌入类型的标识符。我们从第 10 行和第 24 行中的两个结构类型的声明开始,如代码清单 5-51 所示。

代码清单 5-51 listing50.go:第 09 行到第 13 行,第 23 行到第 27 行

09 // user 在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }

23 // admin 代表一个拥有权限的管理员用户
24 type admin struct {
25   user // 嵌入类型
26   level string
27 }

在代码清单 5-51 的第 10 行,我们声明了一个名为 user 的结构类型。在第 24 行,我们声明了另一个名为 admin 的结构类型。在声明 admin 类型的第 25 行,我们将 user 类型嵌入 admin 类型里。要嵌入一个类型,只需要声明这个类型的名字就可以了。在第 26 行,我们声明了一个名为 level 的字段。注意声明字段和嵌入类型在语法上的不同。

一旦我们将 user 类型嵌入 admin ,我们就可以说 user 是外部类型 admin 的内部类型。有了内部类型和外部类型这两个概念,就能更容易地理解这两种类型之间的关系。

代码清单 5-52 展示了使用 user 类型的指针接收者声明名为 notify 的方法。这个方法只是显示一行友好的信息,表示将邮件发给了特定的用户以及邮件地址。

代码清单 5-52 listing50.go:第 15 行到第 21 行

15 // notify 实现了一个可以通过 user 类型值的指针
16 // 调用的方法
17 func (u *user) notify() {
18   fmt.Printf("Sending user email to %s<%s>\n",
19   u.name,
20   u.email)
21 }

现在,让我们来看一下 main 函数,如代码清单 5-53 所示。

代码清单 5-53 listing50.go:第 30 行到第 45 行

30 func main() {
31   // 创建一个 admin 用户
32   ad := admin{
33     user: user{
34       name: "john smith",
35       email: "john@yahoo.com",
36     },
37     level: "super",
38   }
39
40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()
42
43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

代码清单 5-53 中的 main 函数展示了嵌入类型背后的机制。在第 32 行,创建了一个 admin 类型的值。内部类型的初始化是用结构字面量完成的。通过内部类型的名字可以访问内部类型,如代码清单 5-54 所示。对外部类型来说,内部类型总是存在的。这就意味着,虽然没有指定内部类型对应的字段名,还是可以使用内部类型的类型名,来访问到内部类型的值。

代码清单 5-54 listing50.go:第 40 行到第 41 行

40   // 我们可以直接访问内部类型的方法
41   ad.user.notify()

在代码清单 5-54 中第 41 行,可以看到对 notify 方法的调用。这个调用是通过直接访问内部类型 user 来完成的。这展示了内部类型是如何存在于外部类型内,并且总是可访问的。不过,借助内部类型提升, notify 方法也可以直接通过 ad 变量来访问,如代码清单 5-55 所示。

代码清单 5-55 listing50.go:第 43 行到第 45 行

43   // 内部类型的方法也被提升到外部类型
44   ad.notify()
45 }

代码清单 5-55 的第 44 行中展示了直接通过外部类型的变量来调用 notify 方法。由于内部类型的标识符提升到了外部类型,我们可以直接通过外部类型的值来访问内部类型的标识符。让我们修改一下这个例子,加入一个接口,如代码清单 5-56 所示。

代码清单 5-56 listing56.go

01 // 这个示例程序展示如何将嵌入类型应用于接口
02 package main
03
04 import (
05   "fmt"
06 )
07
08 // notifier 是一个定义了
09 // 通知类行为的接口
10 type notifier interface {
11   notify()
12 }
13
14 // user 在程序里定义一个用户类型
15 type user struct {
16   name string
17   email string
18 }
19
20 // notify 实现了一个可以通过 user 类型值
21 // 调用的方法
22 func (u *user) notify() {
23   fmt.Printf("Sending user email to %s<%s>\n",
24   u.name,
25   u.email)
26 }
27
28 // admin 代表一个拥有权限的管理员用户
29 type admin struct {
30   user
31   level string
32 }
33
34 // main 是应用程序的入口
35 func main() {
36   // 创建一个 admin 用户
37   ad := admin{
38     user: user{
39       name: "john smith",
40       email: "john@yahoo.com",
41     },
42     level: "super",
43   }
44
45   // 给 admin 用户发送一个通知
46   // 用于实现接口的内部类型的方法,被提升到
47   // 外部类型
48   sendNotification(&ad)
49 }
50
51 // sendNotification 接受一个实现了 notifier 接口的值
52 // 并发送通知
53 func sendNotification(n notifier) {
54   n.notify()
55 }

代码清单 5-56 所示的示例程序的大部分和之前的程序相同,只有一些小变化,如代码清单 5-57 所示。

代码清单 5-57 第 08 行到第 12 行,第 51 行到第 55 行

08 // notifier 是一个定义了
09 // 通知类行为的接口
10 type notifier interface {
11   notify()
12 }

51 // sendNotification 接受一个实现了 notifier 接口的值
52 // 并发送通知
53 func sendNotification(n notifier) {
54   n.notify()
55 }

在代码清单 5-57 的第 08 行,声明了一个 notifier 接口。之后在第 53 行,有一个 sendNotification 函数,接受 notifier 类型的接口的值。从代码可以知道, user 类型之前声明了名为 notify``的 方法,该方法使用指针接收者实现了 notifier 接口。之后,让我们看一下 main 函数的改动,如代码清单 5-58 所示。

代码清单 5-58 listing56.go:第 35 行到第 49 行

35 func main() {
36   // 创建一个 admin 用户
37   ad := admin{
38     user: user{
39       name: "john smith",
40       email: "john@yahoo.com",
41     },
42     level: "super",
43   }
44
45   // 给 admin 用户发送一个通知
46   // 用于实现接口的内部类型的方法,被提升到
47   // 外部类型
48   sendNotification(&ad)
49 }

这里才是事情变得有趣的地方。在代码清单 5-58 的第 37 行,我们创建了一个名为 ad 的变量,其类型是外部类型 admin 。这个类型内部嵌入了 user 类型。之后第 48 行,我们将这个外部类型变量的地址传给 sendNotification 函数。编译器认为这个指针实现了 notifier 接口,并接受了这个值的传递。不过如果看一下整个示例程序,就会发现 admin 类型并没有实现这个接口。

由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。运行这个示例程序,会得到代码清单 5-59 所示的输出。

代码清单 5-59 listing56.go 的输出

20 // notify 实现了一个可以通过 user 类型值
21 // 调用的方法
22 func (u *user) notify() {
23   fmt.Printf("Sending user email to %s<%s>\n",
24   u.name,
25   u.email)
26 }
 
Output:
Sending user email to john smith<john@yahoo.com>

可以在代码清单 5-59 中看到内部类型的实现被调用。

如果外部类型并不需要使用内部类型的实现,而想使用自己的一套实现,该怎么办?让我们看另一个示例程序是如何解决这个问题的,如代码清单 5-60 所示。

代码清单 5-60 listing60.go

01 // 这个示例程序展示当内部类型和外部类型要
02 // 实现同一个接口时的做法
03 package main
04
05 import (
06   "fmt"
07 )
08
08 // notifier 是一个定义了
09 // 通知类行为的接口
11 type notifier interface {
12   notify()
13 }
14
15 // user 在程序里定义一个用户类型
16 type user struct {
17   name string
18   email string
19 }
20
21 // notify 实现了一个可以通过 user 类型值
22 // 调用的方法
23 func (u *user) notify() {
24   fmt.Printf("Sending user email to %s<%s>\n",
25     u.name,
26     u.email)
27 }
28
29 // admin 代表一个拥有权限的管理员用户
30 type admin struct {
31   user
32   level string
33 }
34
35 // notify 实现了一个可以通过 admin 类型值
36 // 调用的方法
37 func (a *admin) notify() {
38   fmt.Printf("Sending admin email to %s<%s>\n",
39     a.name,
40     a.email)
41 }
42
43 // main 是应用程序的入口
44 func main() {
45   // 创建一个 admin 用户
46   ad := admin{
47     user: user{
48       name: "john smith",
49       email: "john@yahoo.com",
50     },
51     level: "super",
52   }
53
54   // 给 admin 用户发送一个通知
55   // 接口的嵌入的内部类型实现并没有提升到
56   // 外部类型
57   sendNotification(&ad)
58
59   // 我们可以直接访问内部类型的方法
60   ad.user.notify()
61
62   // 内部类型的方法没有被提升
63   ad.notify()
64 }
65
66 // sendNotification 接受一个实现了 notifier 接口的值
67 // 并发送通知
68 func sendNotification(n notifier) {
69   n.notify()
70 }

代码清单 5-60 所示的示例程序的大部分和之前的程序相同,只有一些小变化,如代码清单 5-61 所示。

代码清单 5-61 listing60.go:第 35 行到第 41 行

35 // notify 实现了一个可以通过 admin 类型值
36 // 调用的方法
37 func (a *admin) notify() {
38   fmt.Printf("Sending admin email to %s<%s>\n",
39     a.name,
40     a.email)
41 }

这个示例程序为 admin 类型增加了 notifier 接口的实现。当 admin 类型的实现被调用时,会显示 "Sending admin email" 。作为对比, user 类型的实现被调用时,会显示 "Sending user email"

main 函数里也有一些变化,如代码清单 5-62 所示。

代码清单 5-62 listing60.go:第 43 行到第 64 行

43 // main 是应用程序的入口
44 func main() {
45   // 创建一个 admin 用户
46   ad := admin{
47     user: user{
48       name: "john smith",
49       email: "john@yahoo.com",
50     },
51     level: "super",
52   }
53
54   // 给 admin 用户发送一个通知
55   // 接口的嵌入的内部类型实现并没有提升到
56   // 外部类型
57   sendNotification(&ad)
58
59   // 我们可以直接访问内部类型的方法
60   ad.user.notify()
61
62   // 内部类型的方法没有被提升
63   ad.notify()
64 }

代码清单 5-62 的第 46 行,我们再次创建了外部类型的变量 ad 。在第 57 行,将 ad 变量的地址传给 sendNotification 函数,这个指针实现了接口所需要的方法集。在第 60 行,代码直接访问 user 内部类型,并调用 notify 方法。最后,在第 63 行,使用外部类型变量 ad 来调用 notify 方法。当查看这个示例程序的输出(如代码清单 5-63 所示)时,就会看到区别。

代码清单 5-63 listing60.go 的输出

Sending admin email to john smith<john@yahoo.com>
Sending user email to john smith<john@yahoo.com>
Sending admin email to john smith<john@yahoo.com>

这次我们看到了 admin 类型是如何实现 notifier 接口的,以及如何由 sendNotification 函数以及直接使用外部类型的变量 ad 来执行 admin 类型实现的方法。这表明,如果外部类型实现了 notify 方法,内部类型的实现就不会被提升。不过内部类型的值一直存在,因此还可以通过直接访问内部类型的值,来调用没有被提升的内部类型实现的方法。

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

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

发布评论

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