golang 中的接口

2019-12-06 21:31:17   最后更新: 2019-12-06 22:46:41   访问数量:81




之前的文章中,我们介绍了如何通过 golang 的语法实现面向对象的基本特性

通过 GoLang 实现面向对象思想

 

在文章中,我们介绍了 golang 中一个用于实现抽象的组件 -- 接口,接口是 golang 中非常强大和重要的组件,本文我们就来详细介绍 golang 中接口的用法

 

 

和其他很多语言一样,接口提供了语言的抽象能力,他用来在不暴露数据的内部结构的前提下声明他能够做什么,提供哪些方法

接口的定义很简单,他也以关键字 type 开始:

type InterfaceName interface {

MethodName(<params>) (<returns>)

}

 

他声明了该接口类型的名称,以及符合该接口类型所必须具有的方法列表

 

fmt.Printf

我们常用的 fmt.Printf 方法,他支持传递各种类型的参数,这在不支持重载的 GoLang 语言中看起来很难实现

事实上,fmt.Printf 方法的参数列表如下:

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

 

 

可变参数列表 args 的类型声明为 interface{},起到了通配符的作用,表示可以传递任何类型的对象

 

fmt.Fprintf 与 io.Writer

而他调用的是 fmt 包中的另一个方法 Fprintf:

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

 

 

Fprintf 函数的首个参数的类型是 io.Writer:

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

 

 

只要实现了 Write 方法的类型都可以作为参数传递给 Fprintf 函数,例如 os.Stdout、os.File、bytes.Buffer 或者我们自己定义的实现了 Write 方法的类型都可以作为参数

os.Writer、os.Reader 两个接口被广泛应用在包括文件、内存缓冲区、网络连接、HTTP 客户端、打包器、散列器等一系列可以写入或读取字节的类型的抽象,同时,os 包还提供了用于关闭他们的抽象接口 Closer

 

通过实现 Write 方法构造字符串长度统计类型

结合上面的例子,我们可以构造一个实现字符串长度统计的类型:

type ByteCounter int func (c *ByteCounter) Write(p []byter) (int, error) { *c += ByterCounter(len(p)) return len(p), nil } func main() { var c ByteCounter name := "Kenny" fmt.Frpintf(&c, "hello, %s", name) fmt.Println(c) }

 

 

打印出了 12

 

与 struct 类型一样,接口也支持多个接口的匿名组合生成新的接口类型:

type ReadWriter interface { Reader Writer }

 

 

他相当于:

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

 

 

组合的顺序并不重要,但一个类型必须实现接口中每一个方法,才可以作为该接口类型,这同时也意味着,一个实现了 ReadWriter 接口的类型,同时也是 Reader 和 Writer 类型,而空接口类型 interface{} 对其实现类型没有任何要求,因此我们可以把任何值赋值给空接口类型的变量

var any interface{} any = true any = 12.34 any = make(map[string]interface{}) any["hello"] = new(bytes.Buffer)

 

 

测试接口是否已经被实现

由于接口提供了抽象和动态类型的功能,在代码中动态检测是否符合接口类型是常常会用到的

下面的代码实现了一个检测一个对象是否实现了指定接口的功能:

package main import "fmt" type People interface { GetName() string } type Tom struct { Name string } func (t *Tom) GetName() string { return t.Name } type Dog struct { Name string } func isPeople(instance interface{}) string { if _, ok := instance.(People); ok { return " is a People" } return " is not a People" } func main() { dog := Dog{Name: "dog"} tom := Tom{Name: "tom"} fmt.Println(dog.Name, isPeople(&dog)) fmt.Println(tom.Name, isPeople(&tom)) }

 

 

打印出了:

dog  is not a People

tom  is a People

 

Comma-ok 断言

上面的例子就展示了 comma-ok 断言:

variable.(type)

 

他返回两个值,分别是指定类型的原值,例如:

PeopleTom, ok := tom.(People)

 

 

执行后,PeopleTom 将是一个 People 类型的值或 nil,ok 则返回了检测是否成功的结果

comma-ok 断言并不仅仅可以被用在接口类型的检测中:

package main import ( "fmt" ) type Html []interface{} func main() { html := make(Html, 5) html[0] = "div" html[1] = []byte("script") for index, element := range html { if value, ok := element.(string); ok { fmt.Printf("html[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.([]byte); ok { fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value)) } } }

 

 

打印出了:

html[0] is a string and its value is div html[1] is a []byte and its value is script

 

 

类型转换

基于类型断言,我们就可以实现类型转换了:

package main import ( "fmt" "strings" ) func main() { var a interface{} = "hello world" fmt.Println(strings.ToUpper(a.(string))) }

 

 

打印出了:

HELLO WORLD

 

上面的例子中,由于 strings.ToUpper 只接受一个 string 类型的参数,所以我们不能将 interface{} 类型的变量 a 传递给他,于是我们通过类型断言返回了 string 类型的值

 

需要注意

  1. 上面类型转换的例子中,由于断言忽略了返回的 bool 值,所以一旦转换失败,将会产生 panic
  2. 无论是否接收返回的 bool 值,一旦企图对一个 nil 值进行断言,就一定会产生 panic

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

 






面向对象      oop      接口      interface      golang      抽象     


京ICP备15018585号