之前的文章中,我们介绍了如何通过 golang 的语法实现面向对象的基本特性。 通过 GoLang 实现面向对象思想
在文章中,我们介绍了 golang 中一个用于实现抽象的组件 — 接口,接口是 golang 中非常强大和重要的组件,本文我们就来详细介绍 golang 中接口的用法。
和其他很多语言一样,接口提供了语言的抽象能力,他用来在不暴露数据的内部结构的前提下声明他能够做什么,提供哪些方法。 接口的定义很简单,他也以关键字 type 开始:
type InterfaceName interface { MethodName(<params>) (<returns>) }
他声明了该接口类型的名称,以及符合该接口类型所必须具有的方法列表。
我们常用的 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:
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。
结合上面的例子,我们可以构造一个实现字符串长度统计的类型:
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 断言:
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 类型的值。