Interfaces
Go 中的接口为指定对象的行为提供了一种方法:如果某一个对象能实现这个方法,那么他就可以用作该接口。我们已经见过许多简单的示例了;通过实现 String
方法,我们可以自定义打印函数,而通过 Write
方法,Fprintf
则能对任何对象产生输出。在 Go 代码中, 仅包含一两种方法的接口很常见,且其名称通常来自于实现它的方法, 如 io.Writer
就是实现了 Write
的一类对象。
一个类型可以实现多个接口。例如一个实现了 sort.Interface
接口的集合就可通过 sort
包中的例程进行排序。该接口包括 Len()
、Less(i, j int) bool
以及 Swap(i, j int)
,另外,该集合仍然可以有一个自定义的格式化器。 以下特意构建的例子 Sequence 就同时满足这两种情况。
typeSequence[]int//Methodsrequiredbysort.Interface.func(sSequence)Len()int{returnlen(s)}func(sSequence)Less(i,jint)bool{returns[i]<s[j]}func(sSequence)Swap(i,jint){s[i],s[j]=s[j],s[i]}//CopyreturnsacopyoftheSequence.func(sSequence)Copy()Sequence{copy:=make(Sequence,0,len(s))returnappend(copy,s...)}//Methodforprinting-sortstheelementsbeforeprinting.func(sSequence)String()string{s=s.Copy()//Makeacopy;don'toverwriteargument.sort.Sort(s)str:="["fori,elem:=ranges{//LoopisO(N²);willfixthatinnextexample.ifi>0{str+=""}str+=fmt.Sprint(elem)}returnstr+"]"}
//Copyright2009TheGoAuthors.Allrightsreserved.//UseofthissourcecodeisgovernedbyaBSD-style//licensethatcanbefoundintheLICENSEfile.//go:generategorungenzfunc.go//Packagesortprovidesprimitivesforsortingslicesanduser-definedcollections.packagesort//AnimplementationofInterfacecanbesortedbytheroutinesinthispackage.//Themethodsrefertoelementsoftheunderlyingcollectionbyintegerindex.typeInterfaceinterface{//Lenisthenumberofelementsinthecollection.Len()int//Lessreportswhethertheelementwithindexi//mustsortbeforetheelementwithindexj.////IfbothLess(i,j)andLess(j,i)arefalse,//thentheelementsatindexiandjareconsideredequal.//Sortmayplaceequalelementsinanyorderinthefinalresult,//whileStablepreservestheoriginalinputorderofequalelements.////Lessmustdescribeatransitiveordering://-ifbothLess(i,j)andLess(j,k)aretrue,thenLess(i,k)mustbetrueaswell.//-ifbothLess(i,j)andLess(j,k)arefalse,thenLess(i,k)mustbefalseaswell.////Notethatfloating-pointcomparison(the<operatoronfloat32orfloat64values)//isnotatransitiveorderingwhennot-a-number(NaN)valuesareinvolved.//SeeFloat64Slice.Lessforacorrectimplementationforfloating-pointvalues.Less(i,jint)bool//Swapswapstheelementswithindexesiandj.Swap(i,jint)}
类型转换
Sequence
的String
方法重新实现了Sprint
为切片实现的功能。若我们在调用Sprint
之前将Sequence
转换为纯粹的[]int
,就能复用[]int
实现的String
功能。 这样我们就用的是
func(sSequence)String()string{s=s.Copy()sort.Sort(s)returnfmt.Sprint([]int(s))}
该方法是通过类型转换技术,在 String
方法中安全调用 Sprintf
的另个一例子。若我们忽略类型名的话,这两种类型(Sequence
和 []int
)其实是相同的,因此在二者之间进行转换是合法的。 转换过程并不会创建新值,它只是值暂让现有的时看起来有个新类型而已。 (还有些合法转换则会创建新值,如从整数转换为浮点数等。)
在 Go 程序中,为访问不同的方法集而进行类型转换的情况非常常见。 例如,我们可使用现有的sort.IntSlice
类型来简化整个示例:
typeSequence[]int//Methodforprinting-sortstheelementsbeforeprintingfunc(sSequence)String()string{s=s.Copy()sort.IntSlice(s).Sort()returnfmt.Sprint([]int(s))}
现在,不必让 Sequence 实现多个接口(排序和打印), 我们可通过将对象转换为多种类型(Sequence、sort.IntSlice 和 []int)来使用相应的功能,每次转换都完成一部分工作。 这在实践中虽然有些不同寻常,但往往却很有效。
Interface conversions and type assertions 接口转换与类型断言
类型选择 是类型转换的一种形式:它接受一个接口,在选择 (switch)中根据其判断选择对应的情况(case), 并在某种意义上将其转换为该种类型。以下代码为 fmt.Printf 通过类型选择将值转换为字符串的简化版。若它已经为字符串,我们需要该接口中实际的字符串值; 若它有 String 方法,我们则需要调用该方法所得的结果。
typeStringerinterface{String()string}funcmain(){a:=Sequence{1,2,3,0,4,10}fmt.Println(conversion("123"))//print:123fmt.Println(conversion(a))//print:[0123410]}funcconversion(valueinterface{})string{//Valueprovidedbycaller.switchstr:=value.(type){casestring:returnstrcaseStringer:returnstr.String()}return""}//Methodforprinting-sortstheelementsbeforeprintingfunc(sSequence)String()string{s=s.Copy()sort.IntSlice(s).Sort()returnfmt.Sprint([]int(s))}
第一种情况获取具体的值,第二种将该接口转换为另一个接口。这种方式对于混合类型来说非常完美。
若我们只关心一种类型呢?若我们知道该值拥有一个 string 而想要提取它呢? 只需一种情况的类型选择就行,但它需要类型断言。类型断言接受一个接口值, 并从中提取指定的明确类型的值。其语法借鉴自类型选择开头的子句,但它需要一个明确的类型, 而非 type 关键字:
value.(typeName)
而其结果则是拥有静态类型typeName
的新值。该类型必须为该接口所拥有的具体类型, 或者该值可转换成的第二种接口类型。要提取我们知道在该值中的字符串,可以这样
str,ok:=value.(string)ifok{fmt.Printf("stringvalueis:%q\n",str)}else{fmt.Printf("valueisnotastring\n")}
若类型断言失败,str 将继续存在且为字符串类型,但它将拥有零值,即空字符串。
作为对能量的说明,这里有个 if-else 语句,它等价于本节开头的类型选择。
ifstr,ok:=value.(string);ok{returnstr}elseifstr,ok:=value.(Stringer);ok{returnstr.String()}
Generality
若某种现有的类型仅实现了一个接口,且除此之外并无可导出的方法,则该类型本身就无需导出。 仅导出该接口能让我们更专注于其行为而非实现,其它属性不同的实现则能镜像该原始类型的行为。 这也能够避免为每个通用接口的实例重复编写文档。
依赖于接口而不是实现
面向对象的黄金法则,面向接口编程,而不是实现。在这种情况下,构造函数应当返回一个接口值而非实现的类型。例如在 hash 库中,crc32.NewIEEE 和 adler32.New 都返回接口类型 hash.Hash32。要在 Go 程序中用 Adler-32 算法替代 CRC-32, 只需修改构造函数调用即可,其余代码则不受算法改变的影响。
//NewIEEEcreatesanewhash.Hash32computingtheCRC-32checksumusing//theIEEEpolynomial.ItsSummethodwilllaythevalueoutin//big-endianbyteorder.ThereturnedHash32alsoimplements//encoding.BinaryMarshalerandencoding.BinaryUnmarshalertomarshal//andunmarshaltheinternalstateofthehash.funcNewIEEE()hash.Hash32{returnNew(IEEETable)}//Newreturnsanewhash.Hash32computingtheAdler-32checksum.Its//Summethodwilllaythevalueoutinbig-endianbyteorder.The//returnedHash32alsoimplementsencoding.BinaryMarshalerand//encoding.BinaryUnmarshalertomarshalandunmarshaltheinternal//stateofthehash.funcNew()hash.Hash32{d:=new(digest)d.Reset()returnd}
同样的, crypto
包中多种联系在一起的流密码算法与块密码算法分开。 crypto/cipher
包中的 Block
接口指定了块密码算法的行为, 它为单独的数据块提供加密。接着,和 bufio
包类似,任何实现了该接口的密码包都能被用于构造以 Stream
为接口表示的流密码,而无需知道块加密的细节。
Thecrypto/cipher
interfaces look like this:
typeBlockinterface{BlockSize()intEncrypt(dst,src[]byte)Decrypt(dst,src[]byte)}typeStreaminterface{XORKeyStream(dst,src[]byte)}
这是计数器模式 CTR 流的定义,它将块加密改为流加密,注意块加密的细节已被抽象化了。
//Copyright2009TheGoAuthors.Allrightsreserved.//UseofthissourcecodeisgovernedbyaBSD-style//licensethatcanbefoundintheLICENSEfile.//go:generategorungenzfunc.go//Packagesortprovidesprimitivesforsortingslicesanduser-definedcollections.packagesort//AnimplementationofInterfacecanbesortedbytheroutinesinthispackage.//Themethodsrefertoelementsoftheunderlyingcollectionbyintegerindex.typeInterfaceinterface{//Lenisthenumberofelementsinthecollection.Len()int//Lessreportswhethertheelementwithindexi//mustsortbeforetheelementwithindexj.////IfbothLess(i,j)andLess(j,i)arefalse,//thentheelementsatindexiandjareconsideredequal.//Sortmayplaceequalelementsinanyorderinthefinalresult,//whileStablepreservestheoriginalinputorderofequalelements.////Lessmustdescribeatransitiveordering://-ifbothLess(i,j)andLess(j,k)aretrue,thenLess(i,k)mustbetrueaswell.//-ifbothLess(i,j)andLess(j,k)arefalse,thenLess(i,k)mustbefalseaswell.////Notethatfloating-pointcomparison(the<operatoronfloat32orfloat64values)//isnotatransitiveorderingwhennot-a-number(NaN)valuesareinvolved.//SeeFloat64Slice.Lessforacorrectimplementationforfloating-pointvalues.Less(i,jint)bool//Swapswapstheelementswithindexesiandj.Swap(i,jint)}0
NewCTR 的应用并不仅限于特定的加密算法和数据源,它适用于任何对 Block 接口和 Stream 的实现。因为它们返回接口值, 所以用其它加密模式来代替 CTR 只需做局部的更改。构造函数的调用过程必须被修改, 但由于其周围的代码只能将它看做 Stream,因此它们不会注意到其中的区别。
Interfaces and methods
由于几乎任何类型都能添加方法,因此几乎任何类型都能满足一个接口。一个很直观的例子就是http
包中定义的Handler
接口。任何实现了Handler
的对象都能够处理 HTTP 请求。
ResponseWriter 接口提供了对方法的访问,这些方法需要响应客户端的请求。 由于这些方法包含了标准的 Write 方法,因此 http.ResponseWriter 可用于任何 io.Writer 适用的场景。Request 结构体包含已解析的客户端请求。
为简单起见,我们假设所有的 HTTP 请求都是 GET 方法,而忽略 POST 方法, 这种简化不会影响处理程序的建立方式。这里有个短小却完整的处理程序实现, 它用于记录某个页面被访问的次数。
//Copyright2009TheGoAuthors.Allrightsreserved.//UseofthissourcecodeisgovernedbyaBSD-style//licensethatcanbefoundintheLICENSEfile.//go:generategorungenzfunc.go//Packagesortprovidesprimitivesforsortingslicesanduser-definedcollections.packagesort//AnimplementationofInterfacecanbesortedbytheroutinesinthispackage.//Themethodsrefertoelementsoftheunderlyingcollectionbyintegerindex.typeInterfaceinterface{//Lenisthenumberofelementsinthecollection.Len()int//Lessreportswhethertheelementwithindexi//mustsortbeforetheelementwithindexj.////IfbothLess(i,j)andLess(j,i)arefalse,//thentheelementsatindexiandjareconsideredequal.//Sortmayplaceequalelementsinanyorderinthefinalresult,//whileStablepreservestheoriginalinputorderofequalelements.////Lessmustdescribeatransitiveordering://-ifbothLess(i,j)andLess(j,k)aretrue,thenLess(i,k)mustbetrueaswell.//-ifbothLess(i,j)andLess(j,k)arefalse,thenLess(i,k)mustbefalseaswell.////Notethatfloating-pointcomparison(the<operatoronfloat32orfloat64values)//isnotatransitiveorderingwhennot-a-number(NaN)valuesareinvolved.//SeeFloat64Slice.Lessforacorrectimplementationforfloating-pointvalues.Less(i,jint)bool//Swapswapstheelementswithindexesiandj.Swap(i,jint)}1
在本节中,我们通过一个结构体,一个整数,一个信道和一个函数,建立了一个 HTTP 服务器, 这一切都是因为接口只是方法的集合,而几乎任何类型都能定义方法。