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
接口第一部分的内容到这里就介绍完了。我们将在下一篇教程继续讨论接口的第二部分。感谢阅读。
原文链接: