Go 语言允许用户扩展或者修改已有类型的行为。这个功能对代码复用很重要,在修改已有 类型以符合新类型的时候也很重要。这个功能是通过嵌入类型(type embedding)完成的。嵌入类 型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。
1. 基础概念: 外部类型和内部类型
// user 在程序里定义一个用户类型
type user struct {
name string
email string
}
// admin 代表一个拥有权限的管理员用户
type admin struct {
user
level string
}
user 是外部类型 admin 的内部类型
2. 内部类型提升
// 通过 user 类型值的指针调用的方法
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
}
我们为user定义一个方法,使用指针接收者为其设置。
//我们可以直接访问内部类型的方法
ad.user.notify()
可以看到对 notify 方法的调用。这个调用是通过直接访问内 部类型 user 来完成的。这展示了内部类型是如何存在于外部类型内,并且总是可访问的。
不过, 借助内部类型提升,notify 方法也可以直接通过 ad 变量来访问
// 内部类型的方法也被提升到外部类型
ad.notify()
这就是内部类型提升的一种。
让我们修改一下这个例子,加入一个接口
package main
import (
"fmt"
)
// notifier 是一个定义了通知类行为的接口
type notifier interface {
notify()
}
// user 在程序里定义一个用户类型
type user struct {
name string
email string
}
// 通过 user 类型值的指针调用的方法
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
}
// admin 代表一个拥有权限的管理员用户
type admin struct {
user
level string
}
// main 是应用程序的入口
func main() {
// 创建一个 admin 用户
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
// 给 admin 用户发送一个通知
// 用于实现接口的内部类型的方法,被提升到外部类型
sendNotification(&ad)
}
// sendNotification 接受一个实现了 notifier 接口的值并发送通知
func sendNotification(n notifier) {
n.notify()
}
我们声明了一个 notifier 接口,有一个 sendNotification 函数,接受 notifier 类型的接口的值。从代码可以知道,user实现了接口中的notify方法。
在我们不了解结构体中的内部类型提升时,想到多态的思想时调用sendNotification()方法时
sendNotification(&user) // 参数是实现了接口中的方法的user结构体
不过示例代码却不是这么做的,
sendNotification(&ad) //编译通过,不会报错
这里才是事情变得有趣的地方。我们创建了一个名为 ad 的变 量,其类型是外部类型admin。这个类型内部嵌入了 user 类型。我们将这个外部类型变量的地址传给 sendNotification 函数。编译器认为这个指针实现了 notifier 接口,并接受了这个值的传递。不过如果看一下整个示例程序,就会发现 admin 类型并没有实现这个接口。 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。
这也是类型内部提升的一种。
如果内部类型admin也实现了notifier方法呢?
// 通过 admin 类型值的指针调用的方法
func (a *admin) notify() {
38 fmt.Printf("Sending admin email to %s<%s>\n", 39 a.name, 40 a.email) 41
}
答案是:
如果外部类型 实现了 notify 方法,内部类型的实现就不会被提升。不过内部类型的值一直存在,因此还可以 通过直接访问内部类型的值,来调用没有被提升的内部类型实现的方法。
sendNotification(&ad)
当调用sendNotification()时,执行的admin的方法实现。
当然我们也可以直接方法user实现的方法notify
ad.user.notify()
3. 公开或未公开的标识符
要想设计出好的 API,需要使用某种规则来控制声明后的标识符的可见性. Go语言中通过设置开头的字母大小写控制可见性(变量,结构体,方法等)
package counters
// alertCounter 是一个未公开的类型
type alertCounter int
package main
//编译器会给出错误提示
//不能引用未公开的名字未定义:counters.alertCounter
counters.alertCounter
这里给出了一种解决方法,通过创建New()工厂函数
// New 创建并返回一个未公开的 alertCounter 类型的值
func New(value int) alertCounter {
return alertCounter(value)
}
package main
// 使用 counters 包公开的 New 函数来创建一个未公开的类型的变量
counter := counters.New(10)
这个 New 函数 返回的值被赋给一个名为 counter 的变量。这个程序可以编译并且运行,但为什么呢?New 函 数返回的是一个未公开的 alertCounter 类型的值,而 main 函数能够接受这个值并创建一个未公开的类型的变量
答案是:
在这个示例代码中,alertCounter 是一个未公开的类型,因为它的名称以小写字母开头。但是,New 函数返回了 alertCounter 类型的值,这意味着通过调用 New 函数,我们可以获得一个 alertCounter 类型的值。由于 New 函数是可导出的(它的名称以大写字母开头),所以其他包可以通过调用 New 函数来获取 alertCounter 类型的值,然后使用该值进行操作。
需要注意的是,尽管其他包可以通过调用 New 函数来获取 alertCounter 类型的值,但是它们不能直接访问 alertCounter 类型或其字段,因为它是未公开的。
再来看下面这段代码:
package entities
// user 在程序里定义一个用户类型
type user struct {
Name string
Email string
}
// Admin 在程序里定义了管理员
type Admin struct {
user // 嵌入的类型未公开
Rights int
}
admin中user是未公开的嵌入类型或者说外部类型
func main() {
// 创建 entities 包中的 Admin 类型的值
a := entities.Admin{
Rights: 10,
}
// 设置未公开的内部类型的
// 公开字段的值
a.Name = "Bill"
a.Email = "bill@email.com"
}
这里通过创建 entities 包中的 Admin 类型的值 a
他可以直接访问未公开的结构体user中的值,为什么?
答案是:
内部类型 user 是未公开的,这段代码无法直接通过结构字面量的方式初始化该内部类型,
不过内部类型里声明的字段依旧是公开的!
内部类型的标识符提升到了外部类型,因为 user 类型是未公开的,所以这里没有直接访问内部类型。
参考书籍:《GO语言实战》