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。
type Income interface {calculate() intsource() string}
上面定义的 Income 接口包含两个方法 calculate(),它计算并返回来源的收入,source() 返回来源的条目。
接下来让我们为 FixedBilling 项目类型定义一个结构。
type FixedBilling struct {projectName stringbiddedAmount int}
FixedBilling 项目有两个字段,projectName 表示项目名称,biddedAmount 是组织为项目提供的金额。
TimeAndMaterial 结构将表示时间和材料类型的项目。
type TimeAndMaterial struct {projectName stringnoOfHours inthourlyRate int}
<br />`TimeAndMaterial` 结构有三个字段名称 `projectName`,`noOfHours` 和 `hourlyRate`。
下一步是定义这些结构类型的方法,这些方法计算并返回实际收入和收入来源。
func (fb FixedBilling) calculate() int {return fb.biddedAmount}func (fb FixedBilling) source() string {return fb.projectName}func (tm TimeAndMaterial) calculate() int {return tm.noOfHours * tm.hourlyRate}func (tm TimeAndMaterial) source() string {return tm.projectName}
对于 FixedBilling 项目,收入只是项目的投标金额。因此我们从 FixedBilling 类型的 calculate() 方法返回它。
对于 TimeAndMaterial 项目,收入是 noOfHours 和hourlyRate 的乘积。使用接收器类型 TimeAndMaterial 从 calculate() 方法返回此值。
我们将项目的名称作为 source() 方法的收入来源返回。
由于 FixedBilling 和 TimeAndMaterial 结构都为 Income 接口的 calculate() 和source() 方法提供了定义,因此两个结构都实现了 Income 接口。
让我们声明 calculateNetIncome 函数,它将计算并输出总收入。
func calculateNetIncome(ic []Income) {var netincome int = 0for _, income := range ic {fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())netincome += income.calculate()}fmt.Printf("Net income of organisation = $%d", netincome)}
上面的 calculateNetIncome 函数接受切片 Income 接口作为参数。它通过迭代切片并在每个项目上调用 calculate() 方法来计算总收入。它还通过调用 source() 方法显示收入来源。根据 Income 接口的具体类型,将调用不同的 calculate() 和source() 方法。因此,我们在 calculateNetIncome 函数中实现了多态性。
如果以后添加了一种新的收入来源,这个功能仍然可以正确计算总收入而无需一行代码更改:)。
该计划唯一剩下的部分就是主函数
func main() {project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}incomeStreams := []Income{project1, project2, project3}calculateNetIncome(incomeStreams)}
在上面的 main 函数中,我们创建了三个项目,两个类型为 FixedBilling ,另一个类型为 TimeAndMaterial。接下来,我们使用这 3 个项目创建一个类型为TimeAndMaterial 的切片。由于这些项目中的每一个都实现了 Income 接口,因此可以将所有三个项目添加到一个类型为 Income 的切片中。最后,我们用这个切片调用 calculateNetIncome 函数,它将显示各种收入来源和他们的收入。
这是完整的程序供你参考。
package mainimport ("fmt")type Income interface {calculate() intsource() string}type FixedBilling struct {projectName stringbiddedAmount int}type TimeAndMaterial struct {projectName stringnoOfHours inthourlyRate int}func (fb FixedBilling) calculate() int {return fb.biddedAmount}func (fb FixedBilling) source() string {return fb.projectName}func (tm TimeAndMaterial) calculate() int {return tm.noOfHours * tm.hourlyRate}func (tm TimeAndMaterial) source() string {return tm.projectName}func calculateNetIncome(ic []Income) {var netincome int = 0for _, income := range ic {fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())netincome += income.calculate()}fmt.Printf("Net income of organisation = $%d", netincome)}func main() {project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}incomeStreams := []Income{project1, project2, project3}calculateNetIncome(incomeStreams)}
该程序将输出
Income From Project 1 = $5000Income From Project 2 = $10000Income From Project 3 = $4000Net income of organisation = $19000
在上述计划中添加新的收入流
假设该组织通过广告找到了新的收入来源。让我们看看添加这个新的收入流并计算总收入是多么简单,而不对 calculateNetIncome 函数进行任何更改。由于多态性,这成为可能。
让我们首先定义 Advertisement 类型以及 Advertisement 类型上的 calculate() 和 source() 方法。
type Advertisement struct {adName stringCPC intnoOfClicks int}func (a Advertisement) calculate() int {return a.CPC * a.noOfClicks}func (a Advertisement) source() string {return a.adName}
Advertisement 类型有三个字段 adName、CPC (每次点击的成本) 和 noOfClicks (点击次数)。广告收入的总和是 CPC 和 noOfClicks 的乘积。
让我们稍微修改一下 main 函数,以包含这个新的收入流。
func main() {project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}calculateNetIncome(incomeStreams)}
我们创建了两个广告,即 bannerAd 和 popupAd。 incomeStreams 切片包含我们刚刚创建的两个广告。
这是添加广告后的完整程序。
package mainimport ("fmt")type Income interface {calculate() intsource() string}type FixedBilling struct {projectName stringbiddedAmount int}type TimeAndMaterial struct {projectName stringnoOfHours inthourlyRate int}type Advertisement struct {adName stringCPC intnoOfClicks int}func (fb FixedBilling) calculate() int {return fb.biddedAmount}func (fb FixedBilling) source() string {return fb.projectName}func (tm TimeAndMaterial) calculate() int {return tm.noOfHours * tm.hourlyRate}func (tm TimeAndMaterial) source() string {return tm.projectName}func (a Advertisement) calculate() int {return a.CPC * a.noOfClicks}func (a Advertisement) source() string {return a.adName}func calculateNetIncome(ic []Income) {var netincome int = 0for _, income := range ic {fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())netincome += income.calculate()}fmt.Printf("Net income of organisation = $%d", netincome)}func main() {project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}calculateNetIncome(incomeStreams)}
以上程序将输出,
Income From Project 1 = $5000Income From Project 2 = $10000Income From Project 3 = $4000Income From Banner Ad = $1000Income From Popup Ad = $3750Net income of organisation = $23750
你会注意到虽然我们添加了新的收入流,但我们没有对 calculateNetIncome 函数进行任何更改。它只是因为多态性而起作用。由于新的 Advertisement 类型也实现了Income 接口,我们可以将它添加到 incomeStreams 切片中。  calculateNetIncome 函数也没有任何变化,因为它能够调用 Advertisement 类型的 calculate() 和 source() 方法。
