Go语言中的方法、接口和嵌入类型详解

2019-11-10 08:57:03于海丽

下面定义一个接口:

type Notifier interface {
  Notify() error
}

我们定义了一个叫做 Notifier 的接口并包含一个 Notify 方法。当一个接口只包含一个方法时,按照 Go 语言的约定命名该接口时添加 -er 后缀。这个约定很有用,特别是接口和方法具有相同名字和意义的时候。

我们可以在接口中定义尽可能多的方法,不过在 Go 语言标准库中,你很难找到一个接口包含两个以上的方法。

实现接口

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

让我们继续之前的例子,定义一个函数来接受任意一个实现了接口 Notifier 的类型的值或者指针:

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

SendNotification 函数调用 Notify 方法,这个方法被传入函数的一个值或者指针实现。这样一来一个函数就可以被用来执行任意一个实现了该接口的值或者指针的指定的行为。

用我们的 User 类型来实现该接口并且传入一个 User 类型的值来调用 SendNotification 方法:

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

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

// Output:
cannot use user (type User) as type Notifier in function argument:
User does not implement Notifier (Notify method has pointer receiver)

详细代码:http://play.golang.org/p/KG8-Qb7gqM

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

1.类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集

这条规则说的是如果我们用来调用特定接口方法的接口变量是一个指针类型,那么方法的接受者可以是值类型也可以是指针类型。显然我们的例子不符合该规则,因为我们传入 SendNotification 函数的接口变量是一个值类型。

1.类型 T 的可调用方法集包含接受者为 T 的所有方法

这条规则说的是如果我们用来调用特定接口方法的接口变量是一个值类型,那么方法的接受者必须也是值类型该方法才可以被调用。显然我们的例子也不符合这条规则,因为我们 Notify 方法的接受者是一个指针类型。