Go语言详解 第6章. 接口

接口定义

Go 语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性。指定一个特定类型的值和指针表现为特定的方式。从语言角度看,接口是一种类型,它指定一个方法集,所有方法为接口类型就被认为是该接口。

interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。

interface类型默认是一个指针

interface语法:

  1. type example interface{
  2. Method1(参数列表) 返回值列表
  3. Method2(参数列表) 返回值列表
  4. }
  5. var a example
  6. a.Method1()

定义一个接口:

  1. type Notifier interface {
  2. Notify() error
  3. }

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

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

实现接口

Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type I interface {
  6. Method1() int
  7. Method2() string
  8. }
  9. type S struct {
  10. name string
  11. age int
  12. }
  13. func (s S) Method1() int {
  14. return s.age
  15. }
  16. func (s S) Method2() string {
  17. return s.name
  18. }
  19. func main() {
  20. var user I = S{"Murphy", 28}
  21. age := user.Method1()
  22. name := user.Method2()
  23. fmt.Println(age, name)
  24. }

输出结果:

28 Murphy

如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type I1 interface {
  6. Method1() int
  7. }
  8. type I2 interface {
  9. Method2() string
  10. }
  11. type S struct {
  12. name string
  13. age int
  14. }
  15. func (s S) Method1() int {
  16. return s.age
  17. }
  18. func (s S) Method2() string {
  19. return s.name
  20. }
  21. func main() {
  22. user := S{"Murphy", 28}
  23. age := user.Method1()
  24. name := user.Method2()
  25. fmt.Println(age, name)
  26. }

输出结果:

28 Murphy

如果一个变量只含有了1个interface的部分方法,那么这个变量没有实现这个接口。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type I interface {
  6. Method1() int
  7. Method2() string
  8. }
  9. type S struct {
  10. name string
  11. age int
  12. }
  13. func (s S) Method1() int {
  14. return s.age
  15. }
  16. // func (s S) Method2() string {
  17. // return s.name
  18. // }
  19. func main() {
  20. var user I = S{"Murphy", 28}
  21. age := user.Method1()
  22. // name := user.Method2()
  23. // fmt.Println(age, name)
  24. fmt.Println(age)
  25. }

输出结果:

./main.go:26:6: cannot use S literal (type S) as type I in assignment:

S does not implement I (missing Method2 method)

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

多态:

一种事物的多种形态,都可以按照统一的接口进行操作。

  1. package main
  2. import "fmt"
  3. type Stringer interface {
  4. String() string
  5. }
  6. type Printer interface {
  7. Stringer // 接口嵌入。
  8. Print()
  9. }
  10. type User struct {
  11. id int
  12. name string
  13. }
  14. func (self *User) String() string {
  15. return fmt.Sprintf("user %d, %s", self.id, self.name)
  16. }
  17. func (self *User) Print() {
  18. fmt.Println(self.String())
  19. }
  20. func main() {
  21. var t Printer = &User{1, "Tom"} // *User 方法集包含 String、Print。
  22. t.Print()
  23. }

输出结果:

user 1, Tom

空接口 interface{} 没有任何方法签名,也就意味着任何类型都实现了空接口。其作用类似面向对象语言中的根对象 object。

  1. package main
  2. import "fmt"
  3. func Print(v interface{}) {
  4. fmt.Printf("%T: %v\n", v, v)
  5. }
  6. func main() {
  7. Print(1)
  8. Print("Hello, World!")
  9. }

输出结果:

int: 1

string: Hello, World!

*匿名接口可用作变量类型,或结构成员。

  1. package main
  2. import "fmt"
  3. type Tester struct {
  4. s interface {
  5. String() string
  6. }
  7. }
  8. type User struct {
  9. id int
  10. name string
  11. }
  12. func (self *User) String() string {
  13. return fmt.Sprintf("user %d, %s", self.id, self.name)
  14. }
  15. func main() {
  16. t := Tester{&User{1, "Tom"}}
  17. fmt.Println(t.s.String())
  18. }

输出结果:

user 1, Tom

执行机制

Golang 接口执行机制 :接口对象由接口表 (interface table) 指针和数据指针组成。

接口表存储元数据信息,包括接口类型、动态类型,以及实现接口的方法指针。无论是反射还是通过接口调用方法,都会用到这些信息。

数据指针持有的是目标对象的只读复制品,复制完整对象或指针。

  1. package main
  2. import "fmt"
  3. type User struct {
  4. id int
  5. name string
  6. }
  7. func main() {
  8. u := User{1, "Tom"}
  9. var i interface{} = u
  10. u.id = 2
  11. u.name = "Jack"
  12. fmt.Printf("%v\n", u)
  13. fmt.Printf("%v\n", i.(User))
  14. }

输出结果:

{2 Jack}

{1 Tom}

接口转型返回临时对象,只有使用指针才能修改其状态。

  1. package main
  2. import "fmt"
  3. type User struct {
  4. id int
  5. name string
  6. }
  7. func main() {
  8. u := User{1, "Tom"}
  9. var vi, pi interface{} = u, &u
  10. // vi.(User).name = "Jack"
  11. // Error: cannot assign to vi.(User).name
  12. pi.(*User).name = "Jack"
  13. fmt.Printf("%v\n", vi.(User))
  14. fmt.Printf("%v\n", pi.(*User))
  15. }

输出结果:

{1 Tom}

&{1 Jack}

只有 tab 和 data 都为 nil 时,接口才等于 nil。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "unsafe"
  6. )
  7. var a interface{} = nil
  8. // tab = nil, data = nil
  9. var b interface{} = (*int)(nil)
  10. // tab 包含 *int 类型信息, data = nil
  11. type iface struct {
  12. itab, data uintptr
  13. }
  14. func main() {
  15. ia := *(*iface)(unsafe.Pointer(&a))
  16. ib := *(*iface)(unsafe.Pointer(&b))
  17. fmt.Println(a == nil, ia)
  18. fmt.Println(b == nil, ib, reflect.ValueOf(b).IsNil())
  19. }

输出结果:

true {0 0}

false {17424320 0} true

接口转换

Golang 接口转换 :利用类型推断,可判断接口对象是否某个具体的接口或类型。

  1. package main
  2. import "fmt"
  3. type User struct {
  4. id int
  5. name string
  6. }
  7. func (self *User) String() string {
  8. return fmt.Sprintf("%d, %s", self.id, self.name)
  9. }
  10. func main() {
  11. var o interface{} = &User{1, "Tom"}
  12. if i, ok := o.(fmt.Stringer); ok { // ok-idiom
  13. fmt.Println(i)
  14. }
  15. u := o.(*User)
  16. // u := o.(User)
  17. // panic: interface is *main.User, not main.User
  18. fmt.Println(u)
  19. }

输出结果:

1, Tom

1, Tom

还可用 switch 做批量类型判断,不支持 fallthrough。

  1. package main
  2. import "fmt"
  3. type User struct {
  4. id int
  5. name string
  6. }
  7. func (self *User) String() string {
  8. return fmt.Sprintf("%d, %s", self.id, self.name)
  9. }
  10. func main() {
  11. var o interface{} = &User{1, "Tom"}
  12. switch v := o.(type) {
  13. case nil: // o == nil
  14. fmt.Println("nil")
  15. case fmt.Stringer: // interface
  16. fmt.Println(v)
  17. case func() string: // func
  18. fmt.Println(v())
  19. case *User: // *struct
  20. fmt.Printf("%d, %s\n", v.id, v.name)
  21. default:
  22. fmt.Println("unknown")
  23. }
  24. }

输出结果:

1, Tom

超集接口对象可转换为子集接口,反之出错。

  1. package main
  2. import "fmt"
  3. type Stringer interface {
  4. String() string
  5. }
  6. type Printer interface {
  7. String() string
  8. Print()
  9. }
  10. type User struct {
  11. id int
  12. name string
  13. }
  14. func (self *User) String() string {
  15. return fmt.Sprintf("%d, %v", self.id, self.name)
  16. }
  17. func (self *User) Print() {
  18. fmt.Println(self.String())
  19. }
  20. func main() {
  21. var o Printer = &User{1, "Tom"}
  22. var s Stringer = o
  23. fmt.Println(s.String())
  24. }

输出结果:

1, Tom

接口技巧

Golang 接口技巧 :

让编译器检查,以确保某个类型实现接口。

var _ fmt.Stringer = (*Data)(nil)

  1. package main
  2. import "fmt"
  3. type Data struct {
  4. id int
  5. name string
  6. }
  7. func (self *Data) String() string {
  8. return fmt.Sprintf("%d, %s", self.id, self.name)
  9. }
  10. func main() {
  11. var _ fmt.Stringer = (*Data)(nil)
  12. }

某些时候,让函数直接 “实现” 接口能省不少事。

  1. package main
  2. type Tester interface {
  3. Do()
  4. }
  5. type FuncDo func()
  6. func (self FuncDo) Do() { self() }
  7. func main() {
  8. var t Tester = FuncDo(func() { println("Hello, World!") })
  9. t.Do()
  10. }

输出结果:

Hello, World!

面向对象特性

和其他高级语言一样,golang 也支持面向对象编程,支持得比较简单,有些特性虽不支持,但是够用了。

接口

接口使用 interface 关键字声明,任何实现接口定义方法的类都可以实例化该接口,接口和实现类之间没有任何依赖,你可以实现一个新的类当做 Sayer 来使用,而不需要依赖 Sayer 接口,也可以为已有的类创建一个新的接口,而不需要修改任何已有的代码,和其他静态语言相比,这可以算是 golang 的特色了吧。

  1. type Sayer interface {
  2. Say(message string)
  3. SayHi()
  4. }

继承

继承使用组合的方式实现

  1. type Animal struct {
  2. Name string
  3. }
  4. func (a *Animal) Say(message string) {
  5. fmt.Printf("Animal[%v] say: %v\n", a.Name, message)
  6. }
  7. type Dog struct {
  8. Animal
  9. }

Dog 将继承 Animal 的 Say 方法,以及其成员 Name

覆盖

子类可以重新实现父类的方法

  1. // override Animal.Say
  2. func (d *Dog) Say(message string) {
  3. fmt.Printf("Dog[%v] say: %v\n", d.Name, message)
  4. }

Dog.Say 将覆盖 Animal.Say

多态

接口可以用任何实现该接口的指针来实例化

  1. var sayer Sayer
  2. sayer = &Dog{Animal{Name: "Yoda"}}
  3. sayer.Say("hello world")

但是不支持父类指针指向子类,下面这种写法是不允许的

  1. var animal *Animal
  2. animal = &Dog{Animal{Name: "Yoda"}}

同样子类继承的父类的方法引用的父类的其他方法也没有多态特性

  1. func (a *Animal) Say(message string) {
  2. fmt.Printf("Animal[%v] say: %v\n", a.Name, message)
  3. }
  4. func (a *Animal) SayHi() {
  5. a.Say("Hi")
  6. }
  7. func (d *Dog) Say(message string) {
  8. fmt.Printf("Dog[%v] say: %v\n", d.Name, message)
  9. }
  10. func main() {
  11. var sayer Sayer
  12. sayer = &Dog{Animal{Name: "Yoda"}}
  13. sayer.Say("hello world") // Dog[Yoda] say: hello world
  14. sayer.SayHi() // Animal[Yoda] say: Hi
  15. }

上面这段代码中,子类 Dog 没有实现 SayHi 方法,调用的是从父类 Animal.SayHi,而 Animal.SayHi 调用的是 Animal.Say 而不是Dog.Say,这一点和其他面向对象语言有所区别,需要特别注意,但是可以用下面的方式来实现类似的功能,以提高代码的复用性

  1. func SayHi(s Sayer) {
  2. s.Say("Hi")
  3. }
  4. type Cat struct {
  5. Animal
  6. }
  7. func (c *Cat) Say(message string) {
  8. fmt.Printf("Cat[%v] say: %v\n", c.Name, message)
  9. }
  10. func (c *Cat) SayHi() {
  11. SayHi(c)
  12. }
  13. func main() {
  14. var sayer Sayer
  15. sayer = &Cat{Animal{Name: "Jerry"}}
  16. sayer.Say("hello world") // Cat[Jerry] say: hello world
  17. sayer.SayHi() // Cat[Jerry] say: Hi
  18. }
本文标签:接口定义 接口转换 面向对象