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

使用接口模拟多态性

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

接口类型的变量可以包含实现接口的任何值**。接口的这个属性在 Go 中实现多态性。**

让我们通过一个计算组织净收入的程序来了解 Go 中的多态性。为简单起见,我们假设这个假设的组织有两种项目的收入,固定账单,时间和材料。该组织的净收入按这些项目的收入总和计算。为了简化本教程,我们假设货币是美元,我们不会处理美分。它将使用 int 表示。(我建议阅读 https://forum.golangbridge.org/t/what-is-the-proper-golang-equivalent-to-decimal-when-dealing-with-money/413 来了解如何代表美分。感谢 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. }
  1. <br />`TimeAndMaterial` 结构有三个字段名称 `projectName`,`noOfHours` 和 `hourlyRate`。

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

  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 接口。

让我们声明 calculateNetIncome 函数,它将计算并输出总收入。

  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 函数中实现了多态性。

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

该计划唯一剩下的部分就是主函数

  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 个项目创建一个类型为TimeAndMaterial 的切片。由于这些项目中的每一个都实现了 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 类型以及 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. }

我们创建了两个广告,即 bannerAdpopupAdincomeStreams 切片包含我们刚刚创建的两个广告。

这是添加广告后的完整程序。

  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() 方法。

原文链接

https://golangbot.com/polymorphism/