什么是接口
接口是golang最重要的特性之一,在Go中,接口interface其实和其他语言的接口意思没什么区别。interface理解其为一种类型的规范或者约定。一种类型是不是“实现”了一个接口呢?就看这种类型是不是实现了接口中定义的所有方法。接⼝是一个或多个方法签名的集合,任何非接口类型只要拥有与之对应的全部方法实现 (包括相同的名称、参数列表以及返回值。),就表示它"实现" 了该接口,无需显式在该类型上添加接口声明。此种方式又被称作Duck Type。
接口的实例化
接口是可被实例化的类型,而不仅仅是语言上的约束规范。当我们创建接口变量时,将会为其分配内存,并将赋值给它的对象拷贝存储。将对象赋值给接口变量时,会发生值拷贝行为。没错,接口内部存储的是指向这个复制品的指针。而且我们无法修改这个复制品的状态,也无法获取其指针。
package main
import (
"fmt"
)
type Painter interface {
Draw()
}
type Xiaowang struct {
}
func (Xiaowang) Draw() {
fmt.Println("i am drawing a paper.") //画画
}
func main() {
var xw Xiaowang
var painter Painter = xw
painter.Draw()
var painter2 Painter = &xw
painter2.Draw()
painter3 := Painter(xw)
painter3.Draw()
painter4 := Painter(&xw)
painter4.Draw()
}
此程序中,以上的实例化方法都正确,因为runtime会帮我们调整*和非*。
但如果将Draw前面的接收者改为*Xiaowang,即:
func (*Xiaowang) Draw() {
fmt.Println("i am drawing a paper.") //画画
}
那么非指针的实例化就会出错,提示如下:
.\main.go:25: cannot use xw (type Xiaowang) as type Painter in assignment:
Xiaowang does not implement Painter (Draw method requires pointer receiver)
.\main.go:29: cannot convert xw (type Xiaowang) to type Painter:
Xiaowang does not implement Painter (Draw method requires pointer receiver)
接口的面向类型
在golang中,type很好地实现了一对多,也就是利用这个type关键字可以定义出多种多样的不同类型。而interface很好地支持了多对一,即任何类型都可以被interface类型接收。
Go语言里可以使用type关键字来把一个类型来转换成另外一个类型而保持数据的本质不变,例如:
type Age int
type Height int
type Grade int
type绝不只是对应于C/C++中的typedef,
它不是用于定义一系列的
别名。
它的作用是定义
一系列互不相干的行为特征:通过这些互不相干的行为特征,
本质上同一的事物表现出不同事物的特征。以上面的代码为例:int还是那个int,Age、Height、Grade都是int,但通过type以int为基础类型定义出的不同类型,就完全不相干了;Age、Height、Grade是完全不同的类型(也就是面向不同的对象)。我们可以分别为Age、Height、Grade定义出下列不同的行为(表示为方法或者函数):
func (a Age) Old() bool {
return a > 50
}
func (l Length) NeedTicket() bool {
return l > 120
}
func (g Grade) Pass() bool {
return g >= 60
}
这样就区分开了以int为基础类型的各种衍生类型。再看一个复杂一些的例子:
type Painter interface {
Draw()
}
type Cowboy interface {
Draw()
}
type Xiaoming struct {
}
type XiaomingAsPainter Xiaoming
func (p *XiaomingAsPainter) Draw() {
fmt.Println("I'm painting.")
}
func main() {
var xm Xiaoming
var painter Painter = (*XiaomingAsPainter)(&xm)
painter.Draw()
}
上面的两个接口都包括了Draw()方法,如果类型小明只是一个画家,那么怎么实现画家接口呢?这里就需要理解一个概念:
interface并不是孤立存在的,它是type的抽象。而同一个事物/数据可以有多个type。小明是个人(这里实现为一个struct),画家只是他的一种身份,因此把画家的行为特性独立出来作为一个type,同时也是Painter接口的实现。如果想让他以画家的身份示人,只要把小明转换到画家的身份就可以(通过语句"var painter Painter = (*XiaomingAsPainter)(&xm)")。这样,小明还会被误认为是牛仔吗?显然没有这个可能性。
那么如果小王既是画家又是牛仔,只要让小王同时具有牛仔和画家的身份(类型)就可以:
type Painter interface {
Draw()
}
type Cowboy interface {
Draw()
}
type Xiaowang struct {
}
type XiaowangAsPainter Xiaowang
func (p *XiaowangAsPainter) Draw() {
fmt.Println("I'm painting.")
}
type XiaowangAsCowboy Xiaowang
func (p *XiaowangAsCowboy) Draw() {
fmt.Println("I'm drawing.")
}
func main() {
var xw Xiaowang
var painter Painter = (*XiaowangAsPainter)(&xw)
painter.Draw()
var cowboy Cowboy = (*XiaowangAsCowboy)(&xw)
cowboy.Draw()
}
go的实现把不同类型的行为特性区分开来,又从数据本质的层面把它们联系在一起(都是一个人的不同身份),这充分体现了go语言对对象和数据的理解。
通俗一点说就是定义出不同的类型,再根据这些类型分别定义相应的方法。这样不同的类型就分别实现了不同的接口,虽然这些类型的原始数据可能是一样的。这也体现了面向对象编程的思想。