接口
- 接口关注于类型可以做什么,而不是存储了什么。
- 接口通过列举类型必须满足的一组方法来进行声明。
- 在 Go 语言中,不需要显式声明接口。
- 任何类型的任何值,只要它满足了接口的要求,就能够成为变量t的值
package mainimport ("fmt""strings")type martian struct{}func (m martian) talk() string {return "nack nack"}type laser intfunc (l laser) talk() string {return strings.Repeat("pew ", int(l))}func main() {var t interface {talk() string}t = martian{}fmt.Println(t.talk())t = laser(3)fmt.Println(t.talk())}
接口类型
- 为了复用,通常会把接口声明为类型。
- 按约定,接口名称通常以 er 结尾。
package mainimport ("fmt""strings")type martian struct{}func (m martian) talk() string {return "nack nack"}type laser intfunc (l laser) talk() string {return strings.Repeat("pew ", int(l))}type talker interface {talk() string}func shout(t talker) {louder := strings.ToUpper(t.talk())fmt.Println(louder)}func main() {shout(martian{})shout(laser(2))type crater struct{}// crater does not implement talker (missing talk method)// shout(crater{})}
- 接口可以与 struct 嵌入 特性一同使用。
package mainimport ("fmt""strings")type laser intfunc (l laser) talk() string {return strings.Repeat("pew ", int(l))}type talker interface {talk() string}func shout(t talker) {louder := strings.ToUpper(t.talk())fmt.Println(louder)}func main() {type starship struct {laser}s := starship{laser(3)}fmt.Println(s.talk())shout(s)}
- 同时使用组合和接口将构成非常强大的设计工具。
探索接口
- Go 语言的接口都是隐式满足的。
package mainimport ("fmt""time")type stardater interface {YearDay() intHour() int}// stardate returns a fictional measure of time.func stardate(t stardater) float64 {doy := float64(t.YearDay())h := float64(t.Hour()) / 24.0return 1000 + doy + h}type sol intfunc (s sol) YearDay() int {return int(s % 668)}func (s sol) Hour() int {return 0}func main() {day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)fmt.Printf("%.1f Curiosity has landed\n", stardate(day))s := sol(1422)fmt.Printf("%.1f Happy birthday\n", stardate(s))}
满足接口
- Go 标准库导出了很多只有单个方法的接口。
- Go 通过简单的、通常只有单个方法的接口……来鼓励组合而不是继承,这些接口在各个组件之间形成了简明易懂的界限。
—— Rob Pike - 例如 fmt 包声明的 Stringer 接口:
package mainimport "fmt"// location with a latitude, longitude in decimal degrees.type location struct {lat, long float64}// String formats a location with latitude, longitude.func (l location) String() string {return fmt.Sprintf("%v, %v", l.lat, l.long)}func main() {curiosity := location{-4.5895, 137.4417}fmt.Println(curiosity)}
- 标准库中常用接口还包括:io.Reader,io.Writer,json.Marshaler…
作业题
- 基于刚才小测试的答案,扩展并编写一个用 JSON 格式输出坐标的程序。这个程序的 JSON 输出应该分别用十进制(DD)和度/分/秒(DMS)两种格式提供坐标:

- 通过满足定制 JSON 数据的 json.Marshaler 接口,你应该无需修改坐标结构就能够达到上述目的。在编写 MarshalJSON 方法的时候可以考虑使用 json.Marshal 函数。
- 可以使用 22 章介绍过的 decimal 方法
package mainimport ("encoding/json""fmt""os")// coordinate in degrees, minutes, seconds in a N/S/E/W hemisphere.type coordinate struct {d, m, s float64h rune}// String formats a DMS coordinate.func (c coordinate) String() string {return fmt.Sprintf("%vº%v'%.1f\" %c", c.d, c.m, c.s, c.h)}// decimal converts a d/m/s coordinate to decimal degrees.func (c coordinate) decimal() float64 {sign := 1.0switch c.h {case 'S', 'W', 's', 'w':sign = -1}return sign * (c.d + c.m/60 + c.s/3600)}func (c coordinate) MarshalJSON() ([]byte, error) {return json.Marshal(struct {DD float64 `json:"decimal"`DMS string `json:"dms"`D float64 `json:"degrees"`M float64 `json:"minutes"`S float64 `json:"seconds"`H string `json:"hemisphere"`}{DD: c.decimal(),DMS: c.String(),D: c.d,M: c.m,S: c.s,H: string(c.h),})}// location with a latitude, longitude in decimal degrees.type location struct {Name string `json:"name"`Lat coordinate `json:"latitude"`Long coordinate `json:"longitude"`}func main() {elysium := location{Name: "Elysium Planitia",Lat: coordinate{4, 30, 0.0, 'N'},Long: coordinate{135, 54, 0, 'E'},}bytes, err := json.MarshalIndent(elysium, "", " ")if err != nil {fmt.Println(err)os.Exit(1)}fmt.Println(string(bytes))}
