什么是接口
接口是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 inttype绝不只是对应于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语言对对象和数据的理解。
通俗一点说就是定义出不同的类型,再根据这些类型分别定义相应的方法。这样不同的类型就分别实现了不同的接口,虽然这些类型的原始数据可能是一样的。这也体现了面向对象编程的思想。
有疑问加站长微信联系(非本文作者)