组合

  • 在面向对象的世界中,对象由更小的对象组合而成。
  • 术语:对象组合或组合
  • Go 通过结构体实现组合(composition)。
  • Go 提供了“嵌入”(embedding)特性,它可以实现方法的转发(forwarding)
  • 组合是一种更简单、灵活的方式。

组合结构体

  1. package main
  2. import "fmt"
  3. type report struct {
  4. sol int
  5. high, low float64
  6. lat, long float64
  7. }
  8. func main() {
  9. report := report{sol: 15, high: -1.0, low: -78.0, lat: -4.5895, long: 137.4417}
  10. fmt.Printf("%+v\n", report)
  11. }
  1. package main
  2. import "fmt"
  3. type report struct {
  4. sol int
  5. temperature temperature
  6. location location
  7. }
  8. type temperature struct {
  9. high, low celsius
  10. }
  11. type location struct {
  12. lat, long float64
  13. }
  14. type celsius float64
  15. func main() {
  16. bradbury := location{-4.5895, 137.4417}
  17. t := temperature{high: -1.0, low: -78.0}
  18. report := report{sol: 15, temperature: t, location: bradbury}
  19. fmt.Printf("%+v\n", report)
  20. fmt.Printf("a balmy %vº C\n", report.temperature.high)
  21. }
  1. package main
  2. import "fmt"
  3. type report struct {
  4. sol int
  5. location location
  6. temperature temperature
  7. }
  8. type temperature struct {
  9. high, low celsius
  10. }
  11. type location struct {
  12. lat, long float64
  13. }
  14. type celsius float64
  15. func (t temperature) average() celsius {
  16. return (t.high + t.low) / 2
  17. }
  18. func (r report) average() celsius {
  19. return r.temperature.average()
  20. }
  21. func main() {
  22. t := temperature{high: -1.0, low: -78.0}
  23. fmt.Printf("average %vº C\n", t.average())
  24. report := report{sol: 15, temperature: t}
  25. fmt.Printf("average %vº C\n", report.temperature.average())
  26. }

转发方法

  • Go 可以通过 struct 嵌入 来实现方法的转发。
  • 在 struct 中只给定字段类型,不给定字段名即可。
  1. package main
  2. import "fmt"
  3. type report struct {
  4. sol int
  5. temperature
  6. location
  7. }
  8. type temperature struct {
  9. high, low celsius
  10. }
  11. type location struct {
  12. lat, long float64
  13. }
  14. type celsius float64
  15. func (t temperature) average() celsius {
  16. return (t.high + t.low) / 2
  17. }
  18. func main() {
  19. report := report{
  20. sol: 15,
  21. location: location{-4.5895, 137.4417},
  22. temperature: temperature{high: -1.0, low: -78.0},
  23. }
  24. fmt.Printf("average %vº C\n", report.average())
  25. fmt.Printf("average %vº C\n", report.temperature.average())
  26. fmt.Printf("%vº C\n", report.high)
  27. report.high = 32
  28. fmt.Printf("%vº C\n", report.temperature.high)
  29. }
  • 在 struct 中,可以转发任意类型。
  1. package main
  2. import "fmt"
  3. type sol int
  4. type report struct {
  5. sol
  6. location
  7. temperature
  8. }
  9. type temperature struct {
  10. high, low celsius
  11. }
  12. type location struct {
  13. lat, long float64
  14. }
  15. type celsius float64
  16. func (s sol) days(s2 sol) int {
  17. days := int(s2 - s)
  18. if days < 0 {
  19. days = -days
  20. }
  21. return days
  22. }
  23. func main() {
  24. report := report{sol: 15}
  25. fmt.Println(report.sol.days(1446))
  26. fmt.Println(report.days(1446))
  27. }

命名冲突

  1. package main
  2. import "fmt"
  3. type sol int
  4. type report struct {
  5. sol
  6. location
  7. temperature
  8. }
  9. type temperature struct {
  10. high, low celsius
  11. }
  12. type location struct {
  13. lat, long float64
  14. }
  15. type celsius float64
  16. func (s sol) days(s2 sol) int {
  17. days := int(s2 - s)
  18. if days < 0 {
  19. days = -days
  20. }
  21. return days
  22. }
  23. func (l location) days(l2 location) int {
  24. // To-do: complicated distance calculation
  25. return 5
  26. }
  27. func (r report) days(s2 sol) int {
  28. return r.sol.days(s2)
  29. }
  30. func main() {
  31. report := report{sol: 15}
  32. d := report.days(1446)
  33. fmt.Println(d)
  34. }

继承 还是 组合?

  • Favor object composition over class inheritance.
  • 优先使用对象组合而不是类的继承。
  • Use of classical inheritance is always optional; every problem that it solves can be solved another way.
  • 对传统的继承不是必需的;所有使用继承解决的问题都可以通过其它方法解决。

作业题

组合与转发 - 图1

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type world struct {
  7. radius float64
  8. }
  9. type location struct {
  10. name string
  11. lat, long float64
  12. }
  13. func (l location) description() string {
  14. return fmt.Sprintf("%v (%.1fº, %.1fº)", l.name, l.lat, l.long)
  15. }
  16. type gps struct {
  17. world world
  18. current location
  19. destination location
  20. }
  21. func (g gps) distance() float64 {
  22. return g.world.distance(g.current, g.destination)
  23. }
  24. func (g gps) message() string {
  25. return fmt.Sprintf("%.1f km to %v", g.distance(), g.destination.description())
  26. }
  27. func (w world) distance(p1, p2 location) float64 {
  28. s1, c1 := math.Sincos(rad(p1.lat))
  29. s2, c2 := math.Sincos(rad(p2.lat))
  30. clong := math.Cos(rad(p1.long - p2.long))
  31. return w.radius * math.Acos(s1*s2+c1*c2*clong)
  32. }
  33. func rad(deg float64) float64 {
  34. return deg * math.Pi / 180
  35. }
  36. type rover struct {
  37. gps
  38. }
  39. func main() {
  40. mars := world{radius: 3389.5}
  41. bradbury := location{"Bradbury Landing", -4.5895, 137.4417}
  42. elysium := location{"Elysium Planitia", 4.5, 135.9}
  43. gps := gps{
  44. world: mars,
  45. current: bradbury,
  46. destination: elysium,
  47. }
  48. curiosity := rover{
  49. gps: gps,
  50. }
  51. fmt.Println(curiosity.message())
  52. }