[译] part 28: golang 的面向对象 — 多态

https://juejin.im/post/5cb0a776e51d456e361ed8b6

Go 中的多态性是在接口的帮助下实现的。正如我们已经讨论过的,接口可以在 Go 中隐式实现。如果类型为接口中声明的所有方法提供定义,则类型实现接口。让我们看看在接口的帮助下如何在 Go 中实现多态性。

使用接口实现多态

任何定义了接口的所有方法的类型都被称为隐式实现了该接口。

一个接口类型的变量可以承载任何实现该接口的值。接口的这个属性用于在 Go 中实现多态。

让我们在一个计算组织净收入的程序的帮助下来理解 Go 中的多态性。为简单起见,我们假设这个想象中的组织有两种项目的收入,即固定账单,时间和材料。该组织的净收入按这些项目的收入总和计算。为了简化本教程,我们假设货币是美元,我们不会处理美分。它将使用int表示。 (我建议阅读该文章以了解如何表示美分。感谢 Andreas Matuschek 在评论部分指出了这一点。)

我们定义一个Income结构,

  1. type Income interface {
  2. calculate() int
  3. source() string
  4. }

上面定义的Income接口包含两个方法calculate(),它计算并返回项目来源收入的和,source()返回项目的名称。

下一步定义FixedBilling的结构

  1. type FixedBilling struct {
  2. projectName string
  3. biddedAmount int
  4. }

FixedBilling项目有两个字段projectName,表示项目名称,biddedAmount是组织为项目出价的金额。

TimeAndMaterial结构表示按时间计算收益的项目

  1. type TimeAndMaterial struct {
  2. projectName string
  3. noOfHours int
  4. hourlyRate int
  5. }

TimeAndMaterial结构有三个字段projectNamenoOfHourshourlyRate

下一步是定义这些结构类型的方法,这些方法计算并返回实际收入和收入来源。

  1. func (fb FixedBilling) calculate() int {
  2. return fb.biddedAmount
  3. }
  4. func (fb FixedBilling) source() string {
  5. return fb.projectName
  6. }
  7. func (tm TimeAndMaterial) calculate() int {
  8. return tm.noOfHours * tm.hourlyRate
  9. }
  10. func (tm TimeAndMaterial) source() string {
  11. return tm.projectName
  12. }

对于FixedBilling项目,收入只是项目的投标金额。因此我们从FixedBilling类型的calculate()方法返回它。

对于TimeAndMaterial项目,收入是noOfHourshourlyRate的乘积。返回值来自于使用接收者类型为TimeAndMaterialcalculate()方法。

我们用source()方法来返回收入来源项目的名字。

由于FixedBillingTimeAndMaterial结构都为Income接口的calculate()source()方法提供了定义,因此两个结构都实现了Income接口。

  1. func calculateNetIncome(ic []Income) {
  2. var netincome int = 0
  3. for _, income := range ic {
  4. fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
  5. netincome += income.calculate()
  6. }
  7. fmt.Printf("Net income of organisation = $%d", netincome)
  8. }

上面的calculateNetIncome函数接受Income接口的切片作为参数。它通过迭代切片并在每个项目上调用calculate()方法来计算总收入。它还通过调用source()方法显示收入来源。根据Income接口的具体类型,将调用不同的calculate()source()方法。因此,我们在calculateNetIncome函数中实现了多态性。

在将来,如果添加了一种新的收入来源,这个功能仍然可以正确计算总收入而无需一行代码更改:)。

接下来我们看看 main 函数的内容。

  1. func main() {
  2. project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
  3. project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
  4. project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
  5. incomeStreams := []Income{project1, project2, project3}
  6. calculateNetIncome(incomeStreams)
  7. }

在上面的 main 函数中,我们创建了三个项目,两个类型为FixedBilling,另一个类型为TimeAndMaterial。接下来,我们使用这 3 个项目创建一个类型为Income的切片。由于这些项目中的每一个都实现了Income接口,因此可以将所有三个项目添加到一个类型为Income的片段中。最后,我们用这个切片调用calculateNetIncome函数,它将显示各种收入和收入来源。

整个程序如下,

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Income interface {
  6. calculate() int
  7. source() string
  8. }
  9. type FixedBilling struct {
  10. projectName string
  11. biddedAmount int
  12. }
  13. type TimeAndMaterial struct {
  14. projectName string
  15. noOfHours int
  16. hourlyRate int
  17. }
  18. func (fb FixedBilling) calculate() int {
  19. return fb.biddedAmount
  20. }
  21. func (fb FixedBilling) source() string {
  22. return fb.projectName
  23. }
  24. func (tm TimeAndMaterial) calculate() int {
  25. return tm.noOfHours * tm.hourlyRate
  26. }
  27. func (tm TimeAndMaterial) source() string {
  28. return tm.projectName
  29. }
  30. func calculateNetIncome(ic []Income) {
  31. var netincome int = 0
  32. for _, income := range ic {
  33. fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
  34. netincome += income.calculate()
  35. }
  36. fmt.Printf("Net income of organisation = $%d", netincome)
  37. }
  38. func main() {
  39. project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
  40. project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
  41. project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
  42. incomeStreams := []Income{project1, project2, project3}
  43. calculateNetIncome(incomeStreams)
  44. }

Run in playground

程序输出,

  1. Income From Project 1 = $5000
  2. Income From Project 2 = $10000
  3. Income From Project 3 = $4000
  4. Net income of organisation = $19000

在上述代码中添加新的收入来源

假设该组织通过做广告有了新的收入来源。让我们看看添加这个新的收入来源并计算总收入是多么简单,不需要对calculateNetIncome函数进行任何更改。由于多态性,这成为可能。

让我们首先定义一个Advertisement的收入类型,然后给该类型加上calculate()以及source()方法。

  1. type Advertisement struct {
  2. adName string
  3. CPC int
  4. noOfClicks int
  5. }
  6. func (a Advertisement) calculate() int {
  7. return a.CPC * a.noOfClicks
  8. }
  9. func (a Advertisement) source() string {
  10. return a.adName
  11. }

Advertisement类型有三个字段:adNameCPC(每次点击费用)和noOfClicks(点击次数)。广告的总收入是CPCnoOfClicks的乘积。

让我们稍微修改一下 main 函数,以包含这个新的收入来源。

  1. func main() {
  2. project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
  3. project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
  4. project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
  5. bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}
  6. popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}
  7. incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}
  8. calculateNetIncome(incomeStreams)
  9. }

我们创建了两个名为bannerAdpopupAd的广告收入。 incomeStreams切片包含我们刚刚创建的两个广告。

加上后的整个代码如下,

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Income interface {
  6. calculate() int
  7. source() string
  8. }
  9. type FixedBilling struct {
  10. projectName string
  11. biddedAmount int
  12. }
  13. type TimeAndMaterial struct {
  14. projectName string
  15. noOfHours int
  16. hourlyRate int
  17. }
  18. type Advertisement struct {
  19. adName string
  20. CPC int
  21. noOfClicks int
  22. }
  23. func (fb FixedBilling) calculate() int {
  24. return fb.biddedAmount
  25. }
  26. func (fb FixedBilling) source() string {
  27. return fb.projectName
  28. }
  29. func (tm TimeAndMaterial) calculate() int {
  30. return tm.noOfHours * tm.hourlyRate
  31. }
  32. func (tm TimeAndMaterial) source() string {
  33. return tm.projectName
  34. }
  35. func (a Advertisement) calculate() int {
  36. return a.CPC * a.noOfClicks
  37. }
  38. func (a Advertisement) source() string {
  39. return a.adName
  40. }
  41. func calculateNetIncome(ic []Income) {
  42. var netincome int = 0
  43. for _, income := range ic {
  44. fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
  45. netincome += income.calculate()
  46. }
  47. fmt.Printf("Net income of organisation = $%d", netincome)
  48. }
  49. func main() {
  50. project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
  51. project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
  52. project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
  53. bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}
  54. popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}
  55. incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}
  56. calculateNetIncome(incomeStreams)
  57. }

Run in playground

上述程序输出,

  1. Income From Project 1 = $5000
  2. Income From Project 2 = $10000
  3. Income From Project 3 = $4000
  4. Income From Banner Ad = $1000
  5. Income From Popup Ad = $3750
  6. Net income of organisation = $23750

您会注意到虽然我们添加了新的收入来源,但我们没有对calculateNetIncome函数进行任何更改。这就是多态性的作用。由于新的Advertisement类型也实现了Income接口,我们可以将它添加到incomeStreams切片中。 calculateNetIncome函数也没有任何变化,因为它能够调用Advertisement类型的calculate()source()方法。

ft_authoradmin  ft_create_time2019-08-03 16:39
 ft_update_time2019-08-03 16:39