Golang中的Interface(接口)全面解析_第1页
Golang中的Interface(接口)全面解析_第2页
Golang中的Interface(接口)全面解析_第3页
Golang中的Interface(接口)全面解析_第4页
Golang中的Interface(接口)全面解析_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Golang中的Interface(接⼝),全⾯解析Go语⾔中的interface没有强制要求实现⽅法,但是interface是go中⾮常强⼤的⼯具之⼀。任⼀类型都可以实现interface中的⽅法,interface中的值可以代表是各种类型的值,这就是Go中实现多态的基础什么是接⼝interface就是字⾯意思——接⼝,C++中可以⽤虚基类表⽰;Java中就是erface则是Golang更接近⾯向对象编程范式的另⼀个难点interface是⽅法签名的⼀个集合,这些⽅法可以被任⼀类型通过⽅法实现。因此接⼝就是对象⾏为的申明(不是定义,仅仅表⽰⽅法签名,也可以称作函数原型)。例叫,如果在⼀个接⼝中申明了Walk⽅法签名代表⾛路,Bark⽅法签名表⽰狗叫,然后我们有个结构体对象Dog,我们就说Dog实现了这个接⼝。所以,接⼝的任务就是提供⽅法签名,⽅法签名由⽅法名,输⼊参数,返回值**三部分组成,任⼀类型可以实现这些⽅法,⽐如struct结构体。如果你是⼀个OOP(⾯向对象)类型的程序员,你可能⽤过implement关键字来实现⼀个接⼝,但是在Golang中,你不需要明确的使⽤关键字来表⽰你实现了⼀个接⼝,如果你使⽤的类型实现了⼀个接⼝中的所有⽅法签名,那么Golang就默认你实现了这个接⼝。或多个⽅法,⽅法的名字,参数,返回值和接⼝中的⽅法签名完全⼀致,并且接⼝中的所有⽅法签名在这个类型中都存在,那么我们称为实现了这个接⼝如果我们说⼀个东西,⾛起来像鸭⼦,游泳像鸭⼦,并且叫起来像鸭⼦,那么在Golang中我们就认为它就是⼀只鸭!!申明⼀个接⼝类似struct的申明⽅法,我们需要使⽤interface关键字来定义类型别名来⽅便使⽤接⼝。typeShapeinterface{Area()float64//⾯积Perimeterfloat64//周长}在上⾯的例⼦中,我们定义了⼀个Shape接⼝,其中包含了两个⽅法签名:Area,Perimeter,⽆形参,返回float64值。任何类型,如果实现了和这两个签名形式⼀样*(同样的⽅法名,同样的传参,同样的返回值)*的⽅法,我们就称这个类型实现了Shape这个接⼝由于接⼝是⼀种类似结构体的类型,我们可以创建⼀个Shape类型的变量s。上⾯的例⼦虽然简单,但是包含了很多信息,让我来理理接⼝的概念。接⼝包含两个类型(听上去有点奇奇怪怪)和⼀个值。类型呢,⼀种是静态类型,另⼀种就是动态类型。静态类型就是指接⼝本⾝的类型,⽐如上图中的Shape就是静态类型。值只有动态值(没有静态值)。⼀个接⼝类型的变量只能表⽰实现了这个接⼝的动态类型的值。这个动态类型的变量就是这个接⼝的动态值。(ok,不知道有没有被我绕晕,晕了没事,这⾥做好笔记,继续往下看,回头再来理解这⾥的概念就明⽩了。).从上⾯的例⼦中,我们可以发现,接⼝类型变量值和类型都是nil。这是因为,我们这⾥声明的是Shape变量,它还没有指定动态类型,更没有指定任何动态值。当我们⽤fmt.Println函数的时候,它接受的就是接⼝类型的参数,第⼀个Println的参数就是指向了这个接⼝的动态值,第⼆个Println的参数指向的就是这个接⼝的动态类型。接⼝的实现结构体Rect(实现了以上两个⽅法)在上⾯的程序中,我们定义了⼀个Shape接⼝和Rect结构体。然后Rect实现了Area和Perimeter⽅法,这就实现了Shape接⼝的所有⽅法签名,所以我们就说Rect实现了Shape接⼝(这是Golang默认的,⾃动实现)。但是我们并没有明确的显式指明Rect实现了Shape接⼝,(如果是java语⾔则需要使⽤implement指明实现了某个接⼝,⽐如publicclassRectimplementShape)。当⼀个类型实现了某个接⼝,这个类型的变量也可以⽤它所实现的接⼝类型表⽰(或者说⽤接⼝类型的变量去存放)。我们可以声明⼀个Shape接⼝类型的便令s,然后⽤s去接Rect类型的对象。(上图24,25⾏代码)因为Rect实现了Shape接⼝,所以第25⾏代码是完全有效的。我们可以看到,接⼝变量s的动态类型就是Rect,动态值就是Rect结构体对象{5,4}是固定的,是动态的有时候接⼝的动态类型也叫做具体类型,因为我们获取接⼝类型的时候,它返回的是隐藏的动态值的类型,它的静态类型⼀直是隐式状态。我们可以⽤s调⽤Area⽅法,因为Shape接⼝定义了Area⽅法并且s的具体类型Rect也实现了该⽅法。所以s调⽤的⽅法是动态对象的⽅法。我们也可以⽐较变量s和r的值,因为此时他们动态的类型都是Rect类型,动态值都是{5,4}。让我们改变s的动态类型和动态值:

我们定义了⼀个新的结构体Circle,它也实现了Shape接⼝,所以我们可以给变量s赋⼀个Circle类型的值。我想你现在应该明⽩了为啥接⼝的值和类型都是动态的。猜猜下⾯程序会发⽣什么?上⾯程序中,我们删除了Perimeter⽅法,这个程序就编译不过并且抛出⼀个错误。program.go:22:cannotuseRectliteral(typeRect)astypeShapeinassignment:RectdoesnotimplementShape(missingPerimetermethod)从上⾯的错误中我们可以很容易的理解实现接⼝的要求:我们需要实现接⼝中申明的所有⽅法签名。这也解释了我之前说的要实现接⼝中的所有⽅法,看到这⾥应该有了清晰的理解。空接⼝当⼀个接⼝没有申明任何⽅法签名,它就是空接⼝,⽤**interface{}**表⽰。因为空接⼝没有⽅法签名,所以所有的类型都是隐式实现了空接⼝。现在你知道标准库fmt中的Println函数是如何接收不同类型的参数没?就是使⽤了空接⼝,让我们看看Println的函数签名funcPrintln(a...interface{})(nint,errerror)如你所见,Println是⼀个接收接⼝类型的可变参数函数。下⾯来更深⼊的了解⼀下.我们创建⼀个explain函数,有⼀个空接⼝类型的输⼊参数,⽆返回值。⽤它来解释动态类型和空接⼝。上⾯的程序中,我们创建了⼀个⾃定义字符串类型MyString和⼀个结构体Rect。因为explain函数接收的空接⼝类型的参数,所以我们可以传⼊⼀个MyString,Rect,或者其他类型的变量。因为所有类型实现了空接⼝interface{},所以这样使⽤是合法的。⼜⼀次完美体现了多态的特性。explain的形参i静态类型是接⼝类型,但是它的动态类型是我们传⼊参数的类型。多接⼝上⾯的程序中,我们创建了⼀个有Area⽅法Shape接⼝和⼀个⽤Volume⽅法的Object接⼝。结构体Cube同时实现了这两个⽅法,所以也就同时实现了这两个接⼝。所以我可以把Cube类型的值赋值给Shape和Object接⼝类型的变量。我们指定变量s和o的动态值都是c,我们可以⽤s调⽤Area⽅法,⽤o调⽤Volume⽅法,因为s申明了Area⽅法签名,o申明了Volume⽅法签名,所以这样⽤是合法的。但是如果我们⽤s调⽤Volume⽅法,⽤o调⽤Area⽅法,会发⽣什么呢?fmt.Println("volumeofsofinterfacetypeShapeis",s.Volume())fmt.Println("areaofoofinterfacetypeObjectis",o.Area())然后呢,就出现了⼀下的错误:program.go:31:s.Volumeundefined(typeShapehasnofieldormethodVolume)program.go:32:o.Areaundefined(typeObjecthasnofieldormethodArea)这个程序是编译不过的,因为s的静态类型是Shape,o的静态类型是Object。但是Shape没有定义Volume⽅法,Object没有定义Area⽅法,所以就会产⽣以上错误。为了让上⾯程序正常运⾏,我们需要设法获取这些接⼝的动态值——Cube类型对象(实现了这些接⼝的类型)。这时候我们就要⽤到类型断⾔,下⾯有请类型断⾔。类型断⾔我们创建⼀个变量i,i是接⼝类型,这时候我们需要把i转换成它所代表的的静态类型,我们就可以使⽤语法:i.(Type),Type代表的⽬标类型,这个类型实现了i的静态类型接⼝。Go会检查i的动态类型是不是Type类型,如果是,就返回对应的静态值(静态对象)。上⾯的程序中,Shape类型的变量s的动态值是Cube类型的。我们可以使⽤s.(Cube)语法来获取这个s的动态值,并且赋值给s。这样我们就可以⽤c调⽤Area和Volume⽅法了,因为c是Cube类型的,Cube同时实现了这两个⽅法。注意,如果使⽤类型断⾔语法:i.(Type),但是接⼝变量i得动态值不是Type类型的,也就是说,Type没有实现i的接⼝,那么Go编译器会抛出⼀个编译错误impossibletypeassertion:Typedoesnotimplementi(missingAreamethod)但是,即使Type实现了i的接⼝,如果i没有赋予Type类型的静态值,就是说i=nil,这时候执⾏这个类型断⾔的话,Go就会在程序运⾏过程中抛出⼀个运⾏错误panic:interfaceconversion:main.Shapeisnil,notmain.Cube但是呢,我们还是有⽅法能避免运⾏错误的,需要⽤另⼀种类型断⾔语法:value,ok:=i.(Type)在上⾯的语法中,我们可以⽤bool类型的变量ok来检查i是否指向动态类型Type的值。如果不是,那么ok就是false,并且vlaue就等于nil。这样通过ok来判断,程序在运⾏过程中也不会报错了。因为接⼝变量s的动态值是Cube类型的,⽽Cube⼜实现了Object接⼝,所以第⼀个类型断⾔是成功的。value1的静态类型就是Object,value1也是指向s的动态值{3}(通过Printf的打印结果可以看出来)。但是,因为接⼝变量s的动态类型Cube没有实现Skin类型,所以ok2的值是false,value2的值是nil(接⼝类型的零值)。如果我们使⽤简化的类型断⾔语法“value2:=s.(Skin)”,那么在程序运⾏的时候就会抛出以下的错误:panic:interfaceconversion:main.Cubeisnotmain.Skin:missingmethodColor⚠型对象直接去获取它指向的动态值的属性信息。简⽽⾔之,获取任何接⼝类型没有定义的属性或者⽅法,都会引起运⾏时错误。所以在必要的时候,记得使⽤类型断⾔进⾏转换和判断。类型断⾔不仅仅只是⽤来判断某个接⼝是否指向了某个具体值。我们也⽤来从⼀个接⼝类型到另⼀个接⼝类型的转换(请看上⾯的例⼦:s.(Skin);或者看)。SwitchpackagemainimportfmttypeMyStringstringtypeRectstruct{WidthfloatHeightfloat}funcexplain(iinterface{}){fmt.Printf("valuegivetoexplainfunctionisoftype:%Twithvalue:%v",i,i)}funcmain(){myString:=MyString{"hello,world"}r:=Rect{5.5,4.5}explain(myString)explain(r)}现在,让我们看看空接⼝对的作⽤,上⾯的例⼦是之间空接⼝那⼀节中⽤的例⼦,explain函数有⼀形参是空接⼝类型的,我们可以传任何参数进去。但是如果有⼀个string类型的参数,我们把这个参数传⼊explain函数中,并且把这个字符串的转成所有字母⼤写冰打印。这时候我们该怎么做呢。我们可以⽤string包的ToUpper这个函数,但是这个函数只接受字符串类型的参数,在explain中使⽤ToUpper函数之前,我们需要确认传⼊的接⼝变量的动态类型是否是字符串类型。这时候我们就可以⽤到Switch关键字了。Switch语法很类似之前⽤的类型断⾔的语法:i.(Type),i是⼀个接⼝,type是⼀个内置关键字。这⾥i.(type)得到的是这个接⼝的动态类型,⽽不是类型断⾔⼀节中得到的是动态值。让我们来看个例⼦:在上⾯的例⼦中,我们⽤Switch修改了explain函数,当我们使⽤explain函数的时候,i接收的参数既包含了传⼊参数的动态类型和动态值。在Switch中使⽤i.(type),我们可以获取i的动态类型。然后在Switch中使⽤:case关键字+类型,这种⽅式来判断是否符合i的动态类型。在casestring块中,我们使⽤strings.ToUpper函数来把字符串转换成⼤写模式。但是strings.ToUpper函数只接受字符串类型参数,所以我们需要⽤类型断⾔来获取影藏在i中的动态值。接⼝的嵌套在Go语⾔中,⼀个接⼝不能实现或者扩展另⼀个接⼝,只能通过组合的⽅式,把多个接⼝组合成⼀个新的接⼝。下⾯重写Shape—Cube程序来感受⼀下:上⾯的程序中,因为Cube实现了Area和Volume⽅法,所以Cube同时实现了Shape和Object接⼝。⼜因为Material接⼝是由Shape和Object组合⽽成,所以Cube也实现了Material接⼝。就像匿名嵌套结构体,这是可⾏的,所有内嵌接⼝中的⽅法签名也都属于⽗接⼝,⽗接⼝可以随意访问。指针接收者和值接收者本⽂直到这⾥,所有⽅法都是⽤的值接收者的⽅式。如果使⽤指针接收者,这些程序还是否可⾏?让我们来检验⼀下:在上⾯的程序中,Area⽅法属于*Rect类型,因此Area的接收者会去获取是Rect类型的指针(即使使⽤Rect类型的值去调⽤,底层也会转换成*Rect类型去调⽤)。但是,上诉程序将会编译不通过,go编译器会报编译错误。program.go:27:cannotuseRectliteral(typeRect)astypeShapeinassignment:RectdoesnotimplementShape(Areamethodhaspointerreceiver)这是什么⿁!Rect明明已经实现了Shape接⼝的所有⽅法签名,这是怎么回事呢,Rect表⽰不服!凭什么提⽰RectdoesnotimplementShape。在仔细品品上⾯的错误,后⾯还有⼀句Areamethodhaspointerreceiver。所以Area的接收者是指针类型将会发⽣什么情况呢。⼀个结构类型的⽅法(⽐如上⾯的Rect结构体的Area⽅法),它的接收者⽆论是这个结构体的指针类型,还是值类型,我们都可以⽤r.Area()来调⽤,这是不会报错的,完全合法!但是,在实现接⼝的情况下,可能会有⼀点点的不同。上⾯程序中,接⼝s的动态类型是Rect,Rect类型没有实现Area⽅法,⽽是*Rect类型实现了Area⽅法。定的接收者类型去调⽤该⽅法。但是,在接⼝的实现中,Rect实现的⽅法,不代表***Rect**就实现了该⽅法。就像上⾯的程序,*Rect实现了Area⽅法,但是Rect没有实现Area⽅法,Golang底层不会默认Rect也实现了Area⽅法。所以让上⾯的程序

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论