1、定义
Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。
每个接口类型由数个方法组成。接口的形式代码如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
说明:
- 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
- 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略
2、实现接口的条件
如果一个任意类型 T 的方法集为一个接口类型的方法集的超集,则我们说类型 T 实现了此接口类型。T 可以是一个非接口类型,也可以是一个接口类型。
实现关系在 Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 Go编译器将自动在需要的时候检查两个类型之间的实现关系。
接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。
1)接口的方法与实现接口的类型方法格式一致
在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。
2)接口中所有方法均被实现
当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。
示例:
package main import ( "fmt" ) // 定义一个数据写入器 type DataWriter interface { WriteData(data interface{}) error } // 定义文件结构,用于实现DataWriter type file struct { } // 实现DataWriter接口的WriteData方法 func (d *file) WriteData(data interface{}) error { fmt.Println("WriteData:", data) return nil } func main() { // 实例化file f := new(file) // 声明一个DataWriter的接口 var writer DataWriter // 将接口赋值f,也就是*file类型 writer = f // 使用DataWriter接口进行数据写入 writer.WriteData("data") }
3、类型与接口的关系
在 Go语言中类型和接口之间有一对多和多对一的关系。
1)一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
package main import ( "fmt" "io" ) type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } type Socket struct { } func (s *Socket) Write(p []byte) (n int, err error) { fmt.Println("Socket write") return 0, nil } func (s *Socket) Close() error { fmt.Println("Socket close") return nil } // 使用io.Writer的代码, 并不知道Socket和io.Closer的存在 func usingWriter( writer io.Writer){ writer.Write( nil ) } // 使用io.Closer, 并不知道Socket和io.Writer的存在 func usingCloser( closer io.Closer) { closer.Close() } func main() { // 实例化Socket s := new(Socket) usingWriter(s) usingCloser(s) }
2)多个类型可以实现相同的接口
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。
示例:
package main import "fmt" // 一个服务需要满足能够开启和写日志的功能 type Service interface { Start() // 开启服务 Log(string) // 日志输出 } // 日志器 type Logger struct { } // 实现Service的Log()方法 func (g *Logger) Log(l string) { fmt.Println(l) } // 游戏服务 type GameService struct { Logger // 嵌入日志器 } // 实现Service的Start()方法 func (g *GameService) Start() { fmt.Println("游戏服启动") } func main() { var s Service = new(GameService) s.Start() s.Log("hello") }
4、类型断言
Go语言中有四种接口相关的类型转换情形:
- 将一个非接口值转换为一个接口类型。在这样的转换中,此非接口值的类型必须实现了此接口类型。
- 将一个接口值转换为另一个接口类型(前者接口值的类型实现了后者目标接口类型)。
- 将一个接口值转换为一个非接口类型(此非接口类型必须实现了此接口值的接口类型)。
- 将一个接口值转换为另一个接口类型(前者接口值的类型可以实现了也可以未实现后者目标接口类型)。
一个类型断言表达式的语法为 i.(T),其中 i 为一个接口值, T 为一个类型名或者类型字面表示。 类型 T 可以为任意一个非接口类型,或者一个任意接口类型。
在一个类型断言表达式 i.(T) 中, i 称为断言值, T 称为断言类型。 一个断言可能成功或者失败。
对于 T 是一个非接口类型的情况,如果断言值 i 的动态类型存在并且此动态类型和 T 为同一类型,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为断言值 i 的动态值的一个复制。可以把此种情况看作是一次拆封动态值的尝试。
对于 T 是一个接口类型的情况,当断言值 i 的动态类型存在并且此动态类型实现了接口类型 T,则此断言将成功;否则,此断言失败。 当此断言成功时,此类型断言表达式的估值结果为一个包裹了断言值i的动态值的一个复制的 T 值。
一个失败的类型断言的估值结果为断言类型的零值。
按照上述规则,如果一个类型断言中的断言值是一个零值 nil 接口值,则此断言必定失败。
对于大多数场合,一个类型断言被用做一个单值表达式。 但是,当一个类型断言被用做一个赋值语句中的唯一源值时,此断言可以返回一个可选的第二个结果并被视作为一个多值表达式。此可选的第二个结果为一个类型不确定的布尔值,用来表示此断言是否成功了。
注意:如果一个断言失败并且它的可选的第二个结果未呈现,则此断言将造成一个恐慌。
示例1:断言类型为非接口类型
package main import "fmt" func main() { // 编译器将把123的类型推断为内置类型int。 var x interface{} = 123 // 情形一: n, ok := x.(int) fmt.Println(n, ok) // 123 true n = x.(int) fmt.Println(n) // 123 // 情形二: a, ok := x.(float64) fmt.Println(a, ok) // 0 false // 情形三: a = x.(float64) // 将产生一个恐慌,抛出异常 }
示例2:
package main import "fmt" type Writer interface { Write(buf []byte) (int, error) } type DummyWriter struct{} func (DummyWriter) Write(buf []byte) (int, error) { return len(buf), nil } func main() { var x interface{} = DummyWriter{} // y的动态类型为内置类型string。 var y interface{} = "abc" var w Writer var ok bool // DummyWriter既实现了Writer,也实现了interface{}。 w, ok = x.(Writer) fmt.Println(w, ok) // {} true x, ok = w.(interface{}) fmt.Println(x, ok) // {} true // y的动态类型为string。string类型并没有实现Writer。 w, ok = y.(Writer) fmt.Println(w, ok) // <nil> false w = y.(Writer) // 将产生一个恐慌 }