Go语言的接口

概述

接口是一种抽象类型,是对其他类型行为的概括与抽象,从语法角度来看,接口是一组方法定义的集合。很多面向对象的语言都有接口这个概念,但Go语言接口的独特之处在于它是隐式实现。换句话说,对于一个具体的类型,无须声明它实现了哪些接口,只要提供接口所必需的方法即可。这种设计让编程人员无须改变已有类型的实现就可以为这些类型创建新的接口——对于那些不能修改包的类型,这一点特别有用。

以Go标准库中的fmt.Printf和fmt.Sprintf为例。前者把结果传送到标准输出,后者把结果以string类型返回。输出的实现中格式化是最复杂的部分,通过接口机制可以复用这部分实现。其实两个函数都封装了第三个函数fmt.Fprintf,这个函数对结果输出到哪里并不关心:

package fmt

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)

func Printf(format string, args ...interface{}) (int, error) {
    return Fprintf(os.Stdout, format, args...)
}

func Sprintf(format string, args ...interface{}) string {
    var buf bytes.Buffer
    Fprintf(&buf, format args...)
    return buf.String()
}

Fprintf的第一个参数w是接口类型,其声明如下:

package io

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

io.Writer接口定义了Fprintf和调用者之间的约定。一方面,这个约定要求调用者提供的具体类型(如Printf中的*os.File类型的参数os.Stdout或Sprintf中的*bytes.Buffer类型的参数&buf)包含一个与其签名和行为一致的Write方法。另一方面,这个约定保证了Fprintf能够使用任何满足io.Writer接口的参数。Fprintf只需要能调用参数的Write函数,无须假设它写入的是一个文件还是一段内存。

我们也可以创建一个新类型来实现io.Writer接口:

type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
    *c += ByteCounter(len(p))
    return len(p), nil
}

因为*Bytecounter满足io.Writer接口的约定,所以可以在Fpirntf中使用它:

var c ByteCounter
fmt.Fprintf(&c, "Hello World")
fmt.Println(c) //输出 11

嵌入式接口

一个接口类型定义了一系列方法,如果一个具体类型要实现该接口,那么必须实现接口类型定义中的所有方法。另外,我们也可以通过组合已有接口来得到一个新接口,这样的语法称为嵌入式接口,它让我们可以直接使用一个接口而不用逐一写出这个接口所包含的方法。

package io

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

type Closer interface {
    Close() error
}

type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

实现接口

如果一个类型实现了接口要求的所有方法,那么则称这个类型实现了这个接口。接口的赋值规则也很简单,仅当一个表达式实现了一个接口时,这个表达式才可以赋给该接口。

var w io.Writer

w = os.Stdout //正确,*os.File有Write方法
w = new(bytes.Buffer) //正确,*bytes.Buffer有Write方法
w = time.Second //编译错误,time.Duration没有Write方法

当右侧表达式也是一个接口时,该规则也有效,此时右侧表达式的接口必须实现了左侧接口类型的所有方法才被允许赋值。

空接口

一个不包含任何方法的接口称为空接口。虽然我们无法从空接口中获得任何信息,看起来空接口没有任何用途,但实际上空接口是不可缺少的。正因为空接口类型对其实现类型没有任何要求,所以我们可以把任何值赋给空接口类型。

var any interface{}

any = true
any = 12.34
any = "hello"
any = map[string]int{"one":1}

当然,即使我们创建了一个指向布尔值、浮点数、字符串、map或者其他类型的空接口,也无法直接使用其中的值,毕竟这个接口不包含任何方法。我们需要一个方法从空接口中还原出实际值,用类型断言可以做到。 

接口值

从概念上讲,一个接口类型的值(接口值):一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。对于像Go这样的静态类型语言,类型仅仅是一个编译时的概念,所以类型不是一个值。

在Go语言中,变量总是初始化为一个特定的值,接口也不例外。以下代码将说明这一点:

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

上述代码中,第一行声明了一个接口类型的变量w,此时w的类型和值都为nil——接口的零值就是把它的动态类型和值都设置为nil。一个接口值是否为nil取决于它的动态类型,所以现在这是一个nil接口值,可以与nil相比较。

第二行把一个具体类型隐式转换为一个接口类型,这行代码与 w = io.Writer(os.Stdout) 等价。此时,w的动态类型转换为*os.File,而其值则会设置为os.Stdout的副本,即一个指向代表进程的标准输出的os.File类型的指针。执行这行语句后,调用该接口值的方法,会实际调用(*os.File)的方法,如:w.Write等价于(*os.File).Write。第三行语句也同理。

第四行语句会将w的类型和值都设置为nil,把w恢复到它声明时的状态。

综上可知,接口值可以互相比较,两个接口值的类型和值都是可比较的,且类型和值都相等时才相等。

 

                                                                                                                                                本文部分内容摘自《Go程序设计语言》

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值