Go语言提供了另外一种数据类型,即接口,它把所有具有共性的方法定义在一起,
任何其它类型只要实现了这些方法就是实现了这个接口。
接口代表一种调用契约,是多个方法声明的集合。
在某些动态语言里,接口(interface)也被称作协议(protocol)。
准备交互的双方,共同遵守事先约定的规则,使得无须知道对方身份的情况下进行协作。
接口要实现的是做什么,而不关心怎么做,谁来做。
接口解除了类型依赖,有助于减少用户可视方法,屏蔽内部结构和实现细节。
似乎好处很多,但这并不意味着可以滥用接口,毕竟接口实现机制会有运行开销。
对于相同包,或者不会频繁变化的内部模块结构,并不需要抽象出接口来强行分离。
接口最常见的使用场景是对包外提供访问,或预留扩展空间。
Go的接口类型用于定义一组行为,其中每个行为都由一个方法声明表示。
接口类型中的方法声明只有方法签名而没有方法体,而方法签名包括且仅包括方法的名称、参数列表和结果列表。
举个例子,如果要定义“聊天”相关的一组行为,可以这样写:
type Talk interface { Hello(userName string) string Talk(heard string) (saying string,end bool,err error) }
type、接口类型名称、interface以及由花括号包裹的方法声明集合,共同组成了一个接口类型声明。
注意,其中每个方法声明必须独占一行。
只要一个数据类型的方法集合中包含Talk接口声明的所有方法,那么它就一定是Talk接口的实现类型。
//定义接口 type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } //定义结构体 type struct_name struct { /**/ } //实现接口方法 func (struct_name_var struct_name) method_name1() [return_type] { /*方法实现*/ } func (struct_name_var struct_name) method_name2() [return_type] { /*方法实现*/ }
简单实例:
package main import ( "fmt" ) //定义一个Phone接口 type Phone interface { call() //接口中有一个call方法 } //定义诺基亚结构体 type NokiaPhone struct { } //实现call方法 func (nokkiaPhone NokiaPhone) call() { fmt.Println("I am Nokia") } //定义苹果结构体 type Iphone struct { } //实现call方法 func (iphone Iphone) call() { fmt.Println("I am iphone") } func main() { //根据接口声明变量 var phone Phone //同一个接口变量就能实现多个结构体中的方法 phone = new(NokiaPhone) //创建一个指定类型的值,并返回该值的指针 phone.call() phone = new(Iphone) phone.call() } /* I am Nokia I am iphone */
编译器会根据方法集来判断是否实现了接口。
如果接口没有任何方法声明,那么就是一个空接口(interface{}),
它的用途类似面向对象里面的根类型Object,可被赋值为任何类型的对象。
接口变量默认值是nil,如果实现接口的类型支持,可做相等运算。
package main import "fmt" type tester interface { test() string string() string } type data struct{} //定义结构体 func (*data) test() string { return "5678" } //实现test方法 func (data) string() string { //实现string return "aaaaaaaaa" } func main() { var d data var t tester = &d // fmt.Println(t.test()) //不论是data和*data都是指代同一个程序实体 fmt.Println(t.string()) }
可以像匿名字段那样,嵌入其它接口。
目标类型方法集中必须拥有包含嵌入接口方法在内的全部方法才算实现了该方法。
嵌入其它接口类型,相当于将其声明的方法集导入。
这就要求不能有同名方法,因为不支持重载。还有,不能嵌入自身或循环嵌入,那会导致递归错误。
package main import "fmt" type stringer interface { string() string } type tester interface { test() string stringer } type data struct{} //定义结构体 func (*data) test() string { return "5678" } //实现test方法 func (data) string() string { return "aaaaaaaaa" } func main() { var d data var t tester = &d // fmt.Println(t.test()) fmt.Println(t.string()) } package main import "fmt" type stringer interface { string() string } type tester interface { test() string stringer } type data struct{} func (*data) test() string { return "5678" } func (data) string() string { return "aaaaaaaaa" } func pp(a string) { fmt.Println(a.string()) } func main() { var d data var t tester = &d pp(t) var s stringer = t fmt.Println(s.string()) }
支持匿名接口类型,可直接用于变量定义,或作为结构字段类型。
package main import "fmt" type data struct{} //结构体data func (data) string() string { return "123456" } type node struct { data interface { //字段类型为接口 string() string } } func main() { var t interface { //变量t为接口类型 string() string } = data{} //空结构体 n := node{ data: t, } fmt.Println(n.data.string()) //123456 }
类型推断可将接口变量还原为原始类型,或用来判断是否实现了某个更具体地接口类型。
package main import "fmt" type data int func (d data) String() string { //方法 return fmt.Sprintf("data:%d", d) } func main() { var d data = 15 var x interface{} = d if n, ok := x.(fmt.Stringer); ok { fmt.Println(n) } if d2, ok := x.(data); ok { fmt.Println(d2) } e := x.(error) fmt.Println(e) } /* data:15 data:15 panic: interface conversion: main.data is not error: missing method Error */