Golang

Golang struct、interface 组合嵌入类型详解

2016-07-20  本文已影响5907人  isgiker

概述
在 Go 语言中,如果一个结构体和一个嵌入字段同时实现了相同的接口会发生什么呢?我们猜一下,可能有两个问题:

实现接口
当涉及到我们该怎么让我们的类型实现接口时,Go 语言是特别的一个。Go 语言不需要我们显式的实现类型的接口。如果一个接口里的所有方法都被我们的类型实现了,那么我们就说该类型实现了该接口。

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    user := User{
        Name:  "AriesDevil",
        Email: "ariesdevil@xxoo.com",
    }

    SendNotification(user)
}

实例源码:http://play.golang.org/p/KG8-Qb7gqM
以上代码输出:
prog.go:34: cannot use user (type User) as type Notifier in argument to SendNotification: User does not implement Notifier (Notify method has pointer receiver)

为什么编译器不考虑我们的值是实现该接口的类型?接口的调用规则是建立在这些方法的接受者和接口如何被调用的基础上。下面的是语言规范里定义的规则,这些规则用来说明是否我们一个类型的值或者指针实现了该接口:

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    user := &User{
        Name:  "AriesDevil",
        Email: "ariesdevil@xxoo.com",
    }

    SendNotification(user)
}

嵌入类型
结构体类型可以包含匿名或者嵌入字段。也叫做嵌入一个类型。当我们嵌入一个类型到结构体中时,该类型的名字充当了嵌入字段的字段名。

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

type Admin struct {
    User
    Level string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    admin := &Admin{
        User: User{
            Name:  "AriesDevil",
            Email: "ariesdevil@xxoo.com",
        },
        Level: "super",
    }

    SendNotification(admin)
}

详细代码:http://play.golang.org/p/ivzzzk78TC
事实证明,我们可以 Admin 类型的一个指针来调用 SendNotification 函数。现在 Admin 类型也通过来自嵌入的 User 类型的方法提升实现了该接口。

如果 Admin 类型包含了 User 类型的字段和方法,那么它们在结构体中的关系是怎么样的呢?

当我们嵌入一个类型,这个类型的方法就变成了外部类型的方法,但是当它被调用时,方法的接受者是内部类型(嵌入类型),而非外部类型。— Effective Go

因此嵌入类型的名字充当着字段名,同时嵌入类型作为内部类型存在,我们可以使用下面的调用方法:

admin.User.Notify()
// OutputUser: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

这儿我们通过类型名称来访问内部类型的字段和方法。然而,这些字段和方法也同样被提升到了外部类型:

admin.Notify()
// OutputUser: Sending User Email To AriesDevil<ariesdevil@xxoo.com>

所以通过外部类型来调用 Notify 方法,本质上是内部类型的方法。

下面是 Go 语言中内部类型方法集提升的规则:
给定一个结构体类型 S 和一个命名为 T 的类型,方法提升像下面规定的这样被包含在结构体方法集中:

这条规则说的是当我们嵌入一个类型,嵌入类型的接受者为值类型的方法将被提升,可以被外部类型的值和指针调用。

这条规则说的是当我们嵌入一个类型,可以被外部类型的指针调用的方法集只有嵌入类型的接受者为指针类型的方法集,也就是说,当外部类型使用指针调用内部类型的方法时,只有接受者为指针类型的内部类型方法集将被提升。

这条规则说的是当我们嵌入一个类型的指针,嵌入类型的接受者为值类型或指针类型的方法将被提升,可以被外部类型的值或者指针调用。
这就是语言规范里方法提升中仅有的三条规则,我根据这个推导出一条规则:
如果 S 包含一个匿名字段 T,S 的方法集不包含接受者为 *T 的方法提升。
这条规则说的是当我们嵌入一个类型,嵌入类型的接受者为指针的方法将不能被外部类型的值访问。这也是跟我们上面陈述的接口规则一致。

回答开头的问题
现在我们可以写程序来回答开头提出的两个问题了,首先我们让 Admin 类型实现 Notifier 接口:

Admin 类型实现的接口显示一条 admin 方面的信息。当我们使用 Admin 类型的指针去调用函数 SendNotification 时,这将帮助我们确定到底是哪个接口实现被调用了。

现在创建一个 Admin 类型的值并把它的地址传入 SendNotification 函数,来看看发生了什么:

package main

import (
    "log"
)

type User struct {
    Name  string
    Email string
}

type Admin struct {
    User
    Level string
}

func (u *User) Notify() error {
    log.Printf("User: Sending User Email To %s<%s>\n",
        u.Name,
        u.Email)

    return nil
}

//func (a *Admin) Notify() error {
//  log.Printf("Admin: Sending Admin Email To %s<%s>\n",
//      a.Name,
//      a.Email)
//
//  return nil
//}

type Notifier interface {
    Notify() error
}

func SendNotification(notify Notifier) error {
    return notify.Notify()
}

func main() {
    admin := &Admin{}
    admin.Notify()
    admin.User.Notify()
    SendNotification(admin)
}

源代码实例分享:https://play.golang.org/p/JGhFaJnGpS

转自:http://www.jb51.net/article/56831.htm

上一篇 下一篇

猜你喜欢

热点阅读