首页>>后端>>Golang->Effective Go

Effective Go

时间:2023-11-30 本站 点击:0

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)}

类型转换

SequenceString方法重新实现了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/cipherinterfaces 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 服务器, 这一切都是因为接口只是方法的集合,而几乎任何类型都能定义方法。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Golang/4563.html