Go基础|第11章:interface

  • 原创
  • Madman
  • /
  • /
  • 0
  • 4595 次阅读

Golang.jpg

Synopsis: 接口是用来定义行为的类型,这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。在 Go 语言中,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要接口。当新的类型出现时,更简单方法的、更小的接口更容易满足需求,对于接口设计的一个好的标准就是 ask only for what you need

1. 接口类型

一个 接口(interface) 类型定义了一个 方法集,方法的顺序不重要。比如,标准库 sort 包的 Interface 接口:

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
  // Len is the number of elements in the collection.
  Len() int
  // Less reports whether the element with
  // index i should sort before the element with index j.
  Less(i, j int) bool
  // Swap swaps the elements with indexes i and j.
  Swap(i, j int)
}

或者 io 包的 Reader 接口和 Writer 接口:

type Reader interface {
  Read(p []byte) (n int, err error)
}

type Writer interface {
  Write(p []byte) (n int, err error)
}

Go 语言的接口一般只会描述一个单一的动作,所以像 Reader/Writer 这种接口很常见。如果接口类型只有一个方法,按照惯例,接口名为方法名加上 er

接口也支持嵌入:

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
  Reader
  Writer
}

2. 隐式实现

类型通过实现一个接口的所有方法来实现该接口,一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。实现关系在 Go 中是隐式的,不需要在代码中显式地表示出来:

package main

import (
  "fmt"
  "strconv"
)

type MyStringer interface {
  String() string
}

type Temp int

// 此方法表示类型 Temp 和类型 *Temp 实现了接口 Stringer,但我们无需显式声明此事
func (t Temp) String() string {
  return strconv.Itoa(int(t)) + " °C"
}

type Point struct {
  x, y int
}

// 此方法表示类型 *Point 实现了接口 Stringer,但我们无需显式声明此事
func (p *Point) String() string {
  return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func main() {
  var x MyStringer

  x = Temp(24) // 在 Go 语言中,如果一个类型实现了一个接口的所有方法,那么这个类型的值就可以存储在这个接口类型的值中,不需要额外声明
  fmt.Println(x.String()) // 24 °C

  x = &Point{1, 2}
  fmt.Println(x.String()) // (1, 2)
}

注意: 类型 *Temp 也实现了 MyStringer 接口,因为 *T 类型的方法集包含了使用值接收者声明的方法和使用指针接收者声明的方法

接口的隐式实现机制解耦了接口定义与实现,接口的实现可以出现在任何包中。比如,标准库 fmt 包的 Stringer 接口也有同样的 String() 方法:

// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
  String() string
}

所以,实际上类型 Temp/*Temp/*Point 也都隐式地实现了 fmt.Stringer 接口,可以直接用 fmt.Println 函数打印值:

var x MyStringer

x = Temp(24)
fmt.Println(x) // 24 °C

x = &Point{1, 2}
fmt.Println(x) // (1, 2)

另外,接口的隐式实现机制可以让你创建一个新的接口类型,来满足已经存在的具体类型却不会去改变这些类型的定义,当我们使用的类型来自于不受我们控制的包时(比如标准库),这种设计尤其有用。比如,如果我们声明一个像下面这样的接口类型,则标准库 database/sql 包中声明的 DBTx 类型都自动地实现了这个接口类型,因为它们都拥有此接口类型所指定的三个方法:

import "database/sql"

...

type DatabaseStorer interface {
  Exec(query string, args ...interface{}) (sql.Result, error)
  Prepare(query string) (*sql.Stmt, error)
  Query(query string, args ...interface{}) (*sql.Rows, error)
}

3. 接口值

接口值(interface value) 也可以像其它值一样传递,比如用作函数的参数或返回值。接口值可以看做由两部分组成: 一个具体的类型和此类型的值,它们被称为接口的 具体类型(concrete type)具体值(concrete value)

(type, value)

An interface value holds a value of a specific underlying concrete type.

接口类型的变量可以保存任何实现了这些方法的类型的值,如果 用户定义的类型 实现了某个接口类型声明的所有方法,那么这个用户定义的类型的值就可以赋值给这个接口值。接口值调用方法时会执行其底层具体类型实现的对应方法

var x MyStringer
fmt.Printf("(%T, %v)\n", x, x) // (<nil>, <nil>)  零值

x = Temp(24)
fmt.Printf("(%T, %v)\n", x, x) // (main.Temp, 24 °C)  调用 Temp.String() 方法

x = &Point{1, 2}
fmt.Printf("(%T, %v)\n", x, x) // (*main.Point, (1, 2))  调用 *Point.String() 方法

3.1 nil 接口值: (<nil>, <nil>)

A nil interface value holds neither value nor concrete type.

Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

package main

import "fmt"

type I interface {
  M()
}

func describe(i I) {
  fmt.Printf("(%T, %v)\n", i, i)
}

func main() {
  var i I
  describe(i) // (<nil>, <nil>)

  // panic: runtime error: invalid memory address or nil pointer dereference
  i.M()
}

3.2 具体值为 nil 的接口值: (T, <nil>)

If the concrete value inside the interface itself is nil, the method will be called with a nil receiver. 方法的接收者的值是接口值中的具体值的拷贝,即接收者的值是 nil

package main

import "fmt"

type I interface {
  M()
}

type T struct {
  S string
}

func (t *T) M() { // 方法的接收者 t 是接口值中的具体值的拷贝,即接收者 t 的值是 nil
  if t == nil {
    fmt.Println("<nil>")
    return
  }
  fmt.Println(t.S)
}

func describe(i I) {
  fmt.Printf("(%T, %v)\n", i, i)
}

func main() {
  var i I

  var t *T
  i = t
  describe(i) // (*main.T, <nil>)
  i.M() // <nil>  说明可以调用 (T, <nil>) 的方法

  i = &T{"hello"}
  describe(i) // (*main.T, &{hello})
  i.M() // hello
}

Note than an interface value that holds a nil concrete value is itself non-nil. 具体值为 nil 的接口值不等于 nil,因为具体类型不是 nil

var i I

var t *T
i = t // (*main.T, <nil>)
fmt.Println(i == nil) // false  具体值为 nil 的接口值不等于 nil,因为具体类型是 *T,它不等于 nil

4. 空接口

The interface type that specifies no methods is known as the empty interface.

interface{}

因为每个类型都至少实现了零个方法,所以任何类型都实现了空接口类型,空接口可保存任何类型的值。An empty interface can hold values of any type since every type implements at least zero methods.

var x interface{}

x = 2.4
fmt.Println(x) // 2.4

x = &Point{1, 2}
fmt.Println(x) // (1, 2)

标准库 fmt 包的 Println() 函数可接收任意数量的任意类型参数:

func Println(a ...interface{}) (n int, err error)

5. error 接口

Go 程序使用内建接口 error 的接口值来表示错误:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
  Error() string
}

通常函数会返回一个 error 值,调用它的代码中应当判断这个错误是否等于 nil 来进行错误处理:

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 时表示失败

创建 error 接口值最简单的方法就是调用标准库 errors 包的 New() 函数,它会根据传入的错误信息每次都返回一个新的 error:

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
  return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
  s string
}

func (e *errorString) Error() string {
  return e.s
}

更常用的是使用更方便的封装函数 fmt.Errorf(),它可以处理字符串格式化:

func Errorf(format string, a ...interface{}) error {
  p := newPrinter()
  p.wrapErrs = true
  p.doPrintf(format, a)
  s := string(p.buf)
  var err error
  if p.wrappedErr == nil {
    err = errors.New(s) // here
  } else {
    err = &wrapError{s, p.wrappedErr}
  }
  p.free()
  return err
}

6. 多态

多态(polymorphism) 是指代码可以根据类型的具体实现采取不同行为的能力,换句话说,调用一个接口值的方法实际上将调用此接口值的具体值的对应方法

// Sample program to show what happens when the outer and inner
// type implement the same interface.
package main

import (
  "fmt"
)

// notifier is an interface that defined notification
// type behavior.
type notifier interface {
  notify()
}

// user defines a user in the program.
type user struct {
  name  string
  email string
}

// notify implements a method that can be called via
// a value of type user.
func (u *user) notify() {
  fmt.Printf("Sending user email to %s<%s>\n",
    u.name,
    u.email)
}

// admin represents an admin user with privileges.
type admin struct {
  user // 类型嵌入
  level string
}

// notify implements a method that can be called via
// a value of type Admin.
// 外部类型 admin 不需要内部类型的方法,自己实现了一个新的
func (a *admin) notify() {
  fmt.Printf("Sending admin email to %s<%s>\n",
    a.name,
    a.email)
}

// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) { // 该函数接受一个实现了 notifier 接口的值作为参数,既然任意一个实体类型都能实现 notifier 接口,那么这个函数可以针对任意实体类型的值来执行 notify 方法。因此,这个函数就能提供多态的行为
  n.notify()
}

// main is the entry point for the application.
func main() {
  // Create a user value and pass it to sendNotification.
  alice := user{"Alice", "alice@gmail.com"}
  sendNotification(&alice) // 多态,Sending user email to Alice<alice@gmail.com>

  // Create an admin user.
  ad := admin{
    user: user{
      name:  "john smith",
      email: "john@yahoo.com",
    },
    level: "super",
  }
  // Send the admin user a notification.
  // The embedded inner type's implementation of the
  // interface is NOT "promoted" to the outer type.
  sendNotification(&ad) // 多态,Sending admin email to john smith<john@yahoo.com>

  // We can access the inner type's method directly.
  ad.user.notify() // Sending user email to john smith<john@yahoo.com>

  // The inner type's method is NOT promoted.
  ad.notify() // Sending admin email to john smith<john@yahoo.com>
}

7. 类型断言

类型断言(type assertion) 检查接口值的具体类型是否和断言的类型匹配,并获取接口值的底层具体值:

t := i.(T)

该语句断言接口值 i 的具体类型为 T,并将 i 的具体值赋予变量 t;如果接口值 i 并未保存 T 类型的值,那么该语句会触发 panic 错误

所以,为了判断一个接口值是否保存了一个特定的类型,通常会使用下面的语法格式:

t, ok := i.(T)

该类型断言语句可返回两个值:其底层值以及一个报告断言是否成功的布尔值

  • 若 i 保存了一个 T 类型值,那么 ok 为 true,且 t 将会是其底层值
  • 否则,ok 为 false,而 t 将会是 T 类型的零值,程序并不会触发 panic 错误
package main

import "fmt"

func main() {
  var i interface{} = "hello"

  s := i.(string)
  fmt.Println(s) // hello

  s, ok := i.(string)
  fmt.Println(s, ok) // hello true

  f, ok := i.(float64)
  fmt.Println(f, ok) // 0 false

  f = i.(float64) // panic: interface conversion: interface {} is string, not float64
  fmt.Println(f)
}

8. 类型选择

如果你想根据接口值的具体类型执行不同的操作:

package main

import "fmt"

func do(x interface{}) {
  if x == nil {
    fmt.Println("x is <nil>")
  } else if v, ok := x.(bool); ok {
    fmt.Printf("x is bool: %v\n", v)
  } else if v, ok := x.(string); ok {
    fmt.Printf("x is string: %v\n", v)
  } else if v, ok := x.(int); ok {
    fmt.Printf("x is int: %v\n", v)
  } else {
    fmt.Printf("I don't know about type %T!\n", v)
  }
}

func main() {
  do(nil)
  do(true)
  do("hello")
  do(10)
  do(3.14)
}

if-else 显得很繁琐,可以改用 类型选择(type switch)。A type switch is a construct that permits several type assertions in series.

类型选择与普通的 switch 语句很相似,不过类型选择中的 case 分支为类型名(而非条件表达式值), 它们针对给定接口值所存储的值的类型进行比较:

package main

import (
  "fmt"
)

func do(x interface{}) {
  switch v := x.(type) { // 类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type
  case nil:
    fmt.Println("x is <nil>")
  case bool:
    fmt.Printf("x is bool: %v\n", v)
  case string:
    fmt.Printf("x is string: %v\n", v)
  case int:
    fmt.Printf("x is int: %v\n", v)
  default:
    fmt.Printf("I don't know about type %T!\n", v)
  }
}

func main() {
  do(nil)
  do(true)
  do("hello")
  do(10)
  do(3.14)
}

除了可以使用 %T 格式化符、类型选择 来获取一个值的类型以外,还可以使用标准库 reflect 包:

package main

import (
  "fmt"
  "reflect"
)

func main() {
  var x interface{} = []int{1, 2, 3}
  xType := reflect.TypeOf(x)
  xValue := reflect.ValueOf(x)
  fmt.Println(xType, xValue) // []int [1 2 3]
}
未经允许不得转载: LIFE & SHARE - 王颜公子 » Go基础|第11章:interface

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.

专题系列

文章目录