关于go接口的一些说明

什么是接口

接口是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语言对对象和数据的理解。

通俗一点说就是定义出不同的类型,再根据这些类型分别定义相应的方法。这样不同的类型就分别实现了不同的接口,虽然这些类型的原始数据可能是一样的。这也体现了面向对象编程的思想。

本文来自:开源中国博客

感谢作者:壬癸甲乙

查看原文:关于go接口的一些说明

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。