Go中的多态性是在接口的帮助下实现的。正如我们已经讨论过的,接口在Go中是隐式实现的。如果一个类型为接口中声明的所有方法提供了定义,那么它就实现了该接口。让我们来看看Go中的多态性是如何在接口的帮助下实现的。
使用接口的多态性 任何为某个接口的所有方法提供定义的类型都被认为是隐含地实现了该接口。这一点在我们讨论多态性的例子时将会更加清楚。
一个接口类型的变量可以持有任何实现了该接口的值。接口的这一属性被用来实现Go中的多态性。
让我们借助一个计算一个组织的净收入的程序来理解Go中的多态性。为了简单起见,我们假设这个假想的组织有两种项目的收入,即固定收费和时间与材料。该组织的净收入是由这些项目的收入之和计算的。为了保持本教程的简单性,我们将假设货币为美元,我们将不处理美分。它将使用int来表示。(我建议阅读https://forum.golangbridge.org/t/what-is-the-proper-golang-equivalent-to-decimal-when-dealing-with-money/413,学习如何表示美分)。
让我们首先定义一个Income
接口。
1 2 3 4 type Income interface { calculate() int source() string }
上面定义的Income
接口包含两个方法calculate()
和source()
,前者计算并返回来源的收入,后者返回来源的名称。
接下来,让我们为FixedBilling
项目类型定义一个结构。
1 2 3 4 type FixedBilling struct { projectName string biddedAmount int }
FixedBilling
项目有两个字段projectName
,代表项目名称;biddedAmount
,表示组织对项目的投标金额。
TimeAndMaterial
结构将代表时间和材料类型的项目。
1 2 3 4 5 type TimeAndMaterial struct { projectName string noOfHours int hourlyRate int }
TimeAndMaterial
结构有三个字段:projectName
、noOfHours
和hourlyRate
。
下一步将是在这些结构类型上定义方法,计算并返回实际收入和收入来源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
结构都提供了收入接口的calculate()
和source()
方法的定义,所以这两个结构都实现了Income
接口。
让我们声明calculateNetIncome
函数,它将计算并打印总收入。
1 2 3 4 5 6 7 8 func calculateNetIncome (ic []Income) { var netincome int = 0 for _, income := range ic { fmt.Printf("Income From %s = $%d\n" , income.source(), income.calculate()) netincome += income.calculate() } fmt.Printf("Net income of organization = $%d" , netincome) }
上面的calculateNetIncome
函数接受了一个Income
接口的片断作为参数。它通过遍历该片段并对其中的每一个项目调用calculate()
方法来计算总收入。它还通过调用source()
方法来显示收入来源。根据收入接口的具体类型,不同的calculate()
和source()
方法将被调用。因此,我们已经实现了calculateNetIncome
函数的多态性。
在未来,如果组织增加了一种新的收入来源,这个函数仍然可以正确地计算出总收入,而不需要改变一行代码 :) 。
程序中唯一剩下的部分就是主函数了。
1 2 3 4 5 6 7 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) }
在上面的主函数中,我们创建了三个项目,两个是FixedBilling
类型,一个是TimeAndMaterial
类型。接下来,我们用这3个项目创建一个Income
类型的片断。由于这些项目中的每一个都实现了Income
接口,因此可以将所有三个项目都添加到Income
类型的片断中。最后,我们调用calculateNetIncome
函数,并将这个片断作为参数传给它。它将显示各种收入来源和它们的收入。
下面是完整的程序供你参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport ( "fmt" ) type Income interface { calculate() int source() string } type FixedBilling struct { projectName string biddedAmount int } type TimeAndMaterial struct { projectName string noOfHours int hourlyRate 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 = 0 for _, income := range ic { fmt.Printf("Income From %s = $%d\n" , income.source(), income.calculate()) netincome += income.calculate() } fmt.Printf("Net income of organization = $%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) }
这个程序将输出
1 2 3 4 Income From Project 1 = $5000 Income From Project 2 = $10000 Income From Project 3 = $4000 Net income of organization = $19000
在上述程序中增加一个新的收入来源 假设该组织通过广告找到了一个新的收入来源。让我们看看在不对calculateNetIncome
函数做任何修改的情况下,添加这个新的收入流并计算总收入是多么简单。由于多态性的存在,这成为可能。
让我们首先定义Advertisement
类型以及Advertisement
类型的calculate()
和source()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 type Advertisement struct { adName string CPC int noOfClicks 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
函数,以包括这个新的收入流。
1 2 3 4 5 6 7 8 9 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
片断包括我们刚刚创建的两个广告。
下面是添加Advertisement
后的完整程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package mainimport ( "fmt" ) type Income interface { calculate() int source() string } type FixedBilling struct { projectName string biddedAmount int } type TimeAndMaterial struct { projectName string noOfHours int hourlyRate int } type Advertisement struct { adName string CPC int noOfClicks 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 = 0 for _, income := range ic { fmt.Printf("Income From %s = $%d\n" , income.source(), income.calculate()) netincome += income.calculate() } fmt.Printf("Net income of organization = $%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) }
上述程序将输出。
1 2 3 4 5 6 Income From Project 1 = $5000 Income From Project 2 = $10000 Income From Project 3 = $4000 Income From Banner Ad = $1000 Income From Popup Ad = $3750 Net income of organization = $23750
你会注意到,虽然我们增加了一个新的收入流,但我们并没有对calculateNetIncome
函数做任何改动。它只是因为多态性而发挥作用。由于新的Advertisement
类型也实现了Income
接口,我们能够将其添加到incomeStreams
片断中。calculateNetIncome
函数也没有任何改变,因为它能够调用Advertisement
类型的calculate()
和source()
方法。
这样我们就到了本教程的结尾。祝你有个愉快的一天。