golang接口1
什么是接口
在面向对象语言中,接口一般被定义为:接口定义了一个对象的行为。它仅仅指定了一个对象应该做什么。具体怎么做(实现细节)是由对象决定的。
在Go中,一个接口定义为若干方法的签名。当一个类型定义了所有接口里的方法时,就说这个类型实现了这个接口。这和OOP很像。接口指定了一个类型应该包含什么方法,而该类型决定怎么实现这些方法。
比如WashingMachine
可以作为一个接口,并提供两个函数Cleaning()
和Drying()
。任何提供了Cleaning()
和Drying()
方法定义的类型就可以说它实现了WashingMachine
接口。
声明和实现接口
让我们通过一个程序看一下如何声明和实现一个接口
packagemain import( "fmt" ) //interfacedefinition typeVowelsFinderinterface{ FindVowels()[]rune } typeMyStringstring //MyStringimplementsVowelsFinder func(msMyString)FindVowels()[]rune{ varvowels[]rune for_,rune:=rangems{ ifrune=='a'||rune=='e'||rune=='i'||rune=='o'||rune=='u'{ vowels=append(vowels,rune) } } returnvowels } funcmain(){ name:=MyString("SamAnderson") varvVowelsFinder v=name//possiblesinceMyStringimplementsVowelsFinder fmt.Printf("Vowelsare%c",v.FindVowels()) }12345678910111213141516171819202122232425262728293031
程序的第8行创建了一个接口类型名为VowelsFinder
,它有一个方法FindVowels()[]rune
。
在下一行MyString
类型被创建。
在第15行我们添加了一个方法FindVowels()[]rune
给接受者类型MyString
。现在可以说MyString
实现了VowelsFinder
接口。这和其他语言大大的不同,比如在Java中,一个类必须用implements
关键字显式的标明实现了一个接口。这在Go中是不需要的,在Go中,如果一个类型包含了一个接口声明的所有方法,那么这个类型就隐式地实现了这个接口。
在第28行,我们将MyString
类型的变量name
赋值给VowelsFinder
类型的变量v
。这是合法的,因为MyString
实现了VowelsFinder
。在下一行,v.FindVowels()
在MyString
上调用FindVowels
方法打印在SamAnderson
中所有的元音。程序的输出为:Vowelsare[aeo]
。
恭喜!你已经创建并实现了你的第一个接口。
接口的实际用途
上面的程序告诉我们怎么创建和实现接口,但是没有展示接口的实际用途。在上面的程序中,我们可以使用name.FindVowels()
替代v.FindVowels()
,程序照样可以工作,那么我们为什么还要使用接口呢?
现在让我们来看接口的一个实际用途。
我们将编写一个简单的程序,根据员工的个人工资计算公司的总支出。为了简洁起见,我们假设所有费用都是美元。
packagemain import( "fmt" ) typeSalaryCalculatorinterface{ CalculateSalary()int } typePermanentstruct{ empIdint basicpayint pfint } typeContractstruct{ empIdint basicpayint } //salaryofpermanentemployeeissumofbasicpayandpf func(pPermanent)CalculateSalary()int{ returnp.basicpay+p.pf } //salaryofcontractemployeeisthebasicpayalone func(cContract)CalculateSalary()int{ returnc.basicpay } /* totalexpenseiscalculatedbyiteratingthoughtheSalaryCalculatorsliceandsumming thesalariesoftheindividualemployees */ functotalExpense(s[]SalaryCalculator){ expense:=0 for_,v:=ranges{ expense=expense+v.CalculateSalary() } fmt.Printf("TotalExpensePerMonth$%d",expense) } funcmain(){ pemp1:=Permanent{1,5000,20} pemp2:=Permanent{2,6000,30} cemp1:=Contract{3,3000} employees:=[]SalaryCalculator{pemp1,pemp2,cemp1} totalExpense(employees) }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
上面程序地第7行,定义了一个SalaryCalculator
接口类型,该接口只声明了一个方法:CalculateSalary()int
。
在公司中我们有两种类型的员工:Permanent
和Contract
(第11和17行)。永久性员工的工资是basicpay
和pf
的总和,而合同员工的工资只是基本工资basicpay
。这分别在第23行和第28行的方法CalculateSalary
中表示。通过声明这个方法,Permanent
和Contract
都实现了SalaryCalculator
接口。
第36行中,totalExpense
方法的声明展示了使用接口的好处。这个方法接收一个SalaryCalculator
接口的切片[]SalaryCalculator
作为参数。在第49行,我们将一个包含了Permanent
和Contract
类型的切片给方法totalExpense
。totalExpense
方法出通过调用各自类型的CalculateSalary
方法来计算总支。这是在第39行完成的。
最大的优点是可以将totalExpense
扩展到任何新的员工类型,而不必修改任何代码。假设该公司引入了第三种员工类型Freelancer
和不同的计算工资的方法。那么Freelancer
可以被包含在传递给totalExpense
的切片参数中,而不需要修改任何totalExpense
方法的接口。这个方法会做自己应该做的事情,因为Freelancer
同样实现了SalaryCalculator
接口:)
程序的输出为:TotalExpensePerMonth$14050
。
接口的内部表示
可以把接口想象成这样一个元组(type,value)
。type
是接口包含的具体类型,value
是接口包含的具体的值。
让我们写一个程序来理解这一点。
packagemain import( "fmt" ) typeTestinterface{ Tester() } typeMyFloatfloat64 func(mMyFloat)Tester(){ fmt.Println(m) } funcdescribe(tTest){ fmt.Printf("Interfacetype%Tvalue%v\n",t,t) } funcmain(){ vartTest f:=MyFloat(89.7) t=f describe(t) t.Tester() }123456789101112131415161718192021222324252627
Test
接口提供了一个方法Tester()
,MyFloat
类型实现了这个接口。在第24行,我们将MyFloat
类型的变量f
赋值给Test
类型的变量t
。现在t
的具体类型是MyFloat
而它的值是89.7
。在第17行,describe
函数打印接口的值和具体类型。程序的输出为:
Interfacetypemain.MyFloatvalue89.7
89.712
空接口
一个没有声明任何方法的接口称为空接口。空接口表示为interface{}
。因为空接口没有方法,因此所有类都都实现了空接口。
packagemain import( "fmt" ) funcdescribe(iinterface{}){ fmt.Printf("Type=%T,value=%v\n",i,i) } funcmain(){ s:="HelloWorld" describe(s) i:=55 describe(i) strt:=struct{ namestring }{ name:"NaveenR", } describe(strt) }12345678910111213141516171819202122
上面程序的第7行,describe(iinterface{})
函数接受一个空接口作为参数,因此任何值都可以传递给它。
在第13行,15行,21行,我们传递string,int和结构体给describe
。程序的输出结果为:
Type=string,value=HelloWorld
Type=int,value=55
Type=struct{namestring},value={NaveenR}123
类型断言
类型断言(typeassertion)用来提取接口的实际类型的值。
**i.(T)**是用来获取接口i
的实际类型T
的值的语法。
一个程序胜过千言万语:),让我们写一个类型断言。
packagemain import( "fmt" ) funcassert(iinterface{}){ s:=i.(int)//gettheunderlyingintvaluefromi fmt.Println(s) } funcmain(){ varsinterface{}=56 assert(s) } 123456789101112131415
第12行,s
的实际类型是int
。在第8行我们使用i.(int)
来获取i
的int
值。程序的输出为:56
。
如果实际类型不是int
,那么上面的程序会发生什么?让我们找出来:
packagemain import( "fmt" ) funcassert(iinterface{}){ s:=i.(int) fmt.Println(s) } funcmain(){ varsinterface{}="StevenPaul" assert(s) }1234567891011121314
在上面的程序中,我们将实际类型为string
的变量s
传递给assert
函数,assert
函数尝试从其中提取出一个int
值。该程序会触发panic:panic:interfaceconversion:interface{}isstring,notint
。
为了解决以上问题,我们可以使用下面的语法:
v,ok:=i.(T)1
如果i
的具体类型是T
,则v
将具有i
的实际值,ok
为true
。
如果i
的具体类型不是T
,则ok
将为false
,v
将具有T
的0值,程序不会触发panic。
packagemain import( "fmt" ) funcassert(iinterface{}){ v,ok:=i.(int) fmt.Println(v,ok) } funcmain(){ varsinterface{}=56 assert(s) variinterface{}="StevenPaul" assert(i) }12345678910111213141516
当StevenPaul
传递给assert
函数,ok
将是false
因为i
的实际类型不是int
并且v
的值将是0(int
的0值)。程序将输出:
56true
0false12
TypeSwitch
类型分支(typeswitch)用来将一个接口的具体类型与多个case语句指定的类型进行比较。这很像普通的switch语句。唯一不同的是typeswitch中case指定的是类型,而普通的switch语句中case指定的是值。
typeswitch的语法与类型断言和很相似。在类型断言i.(T)
中,将类型T
替换为关键字type
就变成了typeswitch。让我们通过下面的程序看看它是如何工作的。
packagemain import( "fmt" ) funcfindType(iinterface{}){ switchi.(type){ casestring: fmt.Printf("Iamastringandmyvalueis%s\n",i.(string)) caseint: fmt.Printf("Iamanintandmyvalueis%d\n",i.(int)) default: fmt.Printf("Unknowntype\n") } } funcmain(){ findType("Naveen") findType(77) findType(89.98) }123456789101112131415161718192021
上面的程序中,第8行,switchi.(type)
是一个typeswitch。每一个case
语句都将i
的实际类型和case指定的类型相比较。如果一个case匹配,则打印相应的语句。程序的输出为:
IamastringandmyvalueisNaveen
Iamanintandmyvalueis77
Unknowntype123
在第20行,89.98
是flaot64
类型,并不匹配任何一个case
。因此在会后一行打印:Unknowntype
。
也可以将类型和接口进行比较。如果我们有一个类型,并且该类型实现了一个接口,那么这个类型可以和它实现的接口进行比较。
让我们写一个程序来更清楚地了解这一点。
packagemain import"fmt" typeDescriberinterface{ Describe() } typePersonstruct{ namestring ageint } func(pPerson)Describe(){ fmt.Printf("%sis%dyearsold",p.name,p.age) } funcfindType(iinterface{}){ switchv:=i.(type){ caseDescriber: v.Describe() default: fmt.Printf("unknowntype\n") } } funcmain(){ findType("Naveen") p:=Person{ name:"NaveenR", age:25, } findType(p) }123456789101112131415161718192021222324252627282930313233
在上面的程序中,Person
结构体实现了Describer
接口。在第19行的case语句,v
和Describe
接口进行比较。由于p
实现了Describer
接口因此这个case
匹配成功,在第32行findType(p)
中Person
的Describe()
方法被调用。
程序的输出为:
unknowntype
NaveenRis25yearsold12
接口第一部分的内容到这里就介绍完了。我们将在下一篇教程继续讨论接口的第二部分。感谢阅读。
原文链接: