接口

  • 接口关注于类型可以做什么,而不是存储了什么。
  • 接口通过列举类型必须满足的一组方法来进行声明。
  • 在 Go 语言中,不需要显式声明接口。
  • 任何类型的任何值,只要它满足了接口的要求,就能够成为变量t的值
  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type martian struct{}
  7. func (m martian) talk() string {
  8. return "nack nack"
  9. }
  10. type laser int
  11. func (l laser) talk() string {
  12. return strings.Repeat("pew ", int(l))
  13. }
  14. func main() {
  15. var t interface {
  16. talk() string
  17. }
  18. t = martian{}
  19. fmt.Println(t.talk())
  20. t = laser(3)
  21. fmt.Println(t.talk())
  22. }

接口类型

  • 为了复用,通常会把接口声明为类型。
  • 按约定,接口名称通常以 er 结尾。
  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type martian struct{}
  7. func (m martian) talk() string {
  8. return "nack nack"
  9. }
  10. type laser int
  11. func (l laser) talk() string {
  12. return strings.Repeat("pew ", int(l))
  13. }
  14. type talker interface {
  15. talk() string
  16. }
  17. func shout(t talker) {
  18. louder := strings.ToUpper(t.talk())
  19. fmt.Println(louder)
  20. }
  21. func main() {
  22. shout(martian{})
  23. shout(laser(2))
  24. type crater struct{}
  25. // crater does not implement talker (missing talk method)
  26. // shout(crater{})
  27. }
  • 接口可以与 struct 嵌入 特性一同使用。
  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type laser int
  7. func (l laser) talk() string {
  8. return strings.Repeat("pew ", int(l))
  9. }
  10. type talker interface {
  11. talk() string
  12. }
  13. func shout(t talker) {
  14. louder := strings.ToUpper(t.talk())
  15. fmt.Println(louder)
  16. }
  17. func main() {
  18. type starship struct {
  19. laser
  20. }
  21. s := starship{laser(3)}
  22. fmt.Println(s.talk())
  23. shout(s)
  24. }
  • 同时使用组合和接口将构成非常强大的设计工具。

探索接口

  • Go 语言的接口都是隐式满足的。
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. type stardater interface {
  7. YearDay() int
  8. Hour() int
  9. }
  10. // stardate returns a fictional measure of time.
  11. func stardate(t stardater) float64 {
  12. doy := float64(t.YearDay())
  13. h := float64(t.Hour()) / 24.0
  14. return 1000 + doy + h
  15. }
  16. type sol int
  17. func (s sol) YearDay() int {
  18. return int(s % 668)
  19. }
  20. func (s sol) Hour() int {
  21. return 0
  22. }
  23. func main() {
  24. day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
  25. fmt.Printf("%.1f Curiosity has landed\n", stardate(day))
  26. s := sol(1422)
  27. fmt.Printf("%.1f Happy birthday\n", stardate(s))
  28. }

满足接口

  • Go 标准库导出了很多只有单个方法的接口。
  • Go 通过简单的、通常只有单个方法的接口……来鼓励组合而不是继承,这些接口在各个组件之间形成了简明易懂的界限。
    —— Rob Pike
  • 例如 fmt 包声明的 Stringer 接口:

接口 - 图1

  1. package main
  2. import "fmt"
  3. // location with a latitude, longitude in decimal degrees.
  4. type location struct {
  5. lat, long float64
  6. }
  7. // String formats a location with latitude, longitude.
  8. func (l location) String() string {
  9. return fmt.Sprintf("%v, %v", l.lat, l.long)
  10. }
  11. func main() {
  12. curiosity := location{-4.5895, 137.4417}
  13. fmt.Println(curiosity)
  14. }
  • 标准库中常用接口还包括:io.Reader,io.Writer,json.Marshaler…

作业题

  • 基于刚才小测试的答案,扩展并编写一个用 JSON 格式输出坐标的程序。这个程序的 JSON 输出应该分别用十进制(DD)和度/分/秒(DMS)两种格式提供坐标:

接口 - 图2

  • 通过满足定制 JSON 数据的 json.Marshaler 接口,你应该无需修改坐标结构就能够达到上述目的。在编写 MarshalJSON 方法的时候可以考虑使用 json.Marshal 函数。
  • 可以使用 22 章介绍过的 decimal 方法
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. )
  7. // coordinate in degrees, minutes, seconds in a N/S/E/W hemisphere.
  8. type coordinate struct {
  9. d, m, s float64
  10. h rune
  11. }
  12. // String formats a DMS coordinate.
  13. func (c coordinate) String() string {
  14. return fmt.Sprintf("%vº%v'%.1f\" %c", c.d, c.m, c.s, c.h)
  15. }
  16. // decimal converts a d/m/s coordinate to decimal degrees.
  17. func (c coordinate) decimal() float64 {
  18. sign := 1.0
  19. switch c.h {
  20. case 'S', 'W', 's', 'w':
  21. sign = -1
  22. }
  23. return sign * (c.d + c.m/60 + c.s/3600)
  24. }
  25. func (c coordinate) MarshalJSON() ([]byte, error) {
  26. return json.Marshal(struct {
  27. DD float64 `json:"decimal"`
  28. DMS string `json:"dms"`
  29. D float64 `json:"degrees"`
  30. M float64 `json:"minutes"`
  31. S float64 `json:"seconds"`
  32. H string `json:"hemisphere"`
  33. }{
  34. DD: c.decimal(),
  35. DMS: c.String(),
  36. D: c.d,
  37. M: c.m,
  38. S: c.s,
  39. H: string(c.h),
  40. })
  41. }
  42. // location with a latitude, longitude in decimal degrees.
  43. type location struct {
  44. Name string `json:"name"`
  45. Lat coordinate `json:"latitude"`
  46. Long coordinate `json:"longitude"`
  47. }
  48. func main() {
  49. elysium := location{
  50. Name: "Elysium Planitia",
  51. Lat: coordinate{4, 30, 0.0, 'N'},
  52. Long: coordinate{135, 54, 0, 'E'},
  53. }
  54. bytes, err := json.MarshalIndent(elysium, "", " ")
  55. if err != nil {
  56. fmt.Println(err)
  57. os.Exit(1)
  58. }
  59. fmt.Println(string(bytes))
  60. }