欢迎来到Golang系列教程
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
。
type Income interface {
calculate() int
source() string
}
上面的 Income
接口定义包含两个方法:calculate()
用来计算并返回 source 的收入,而 source()
返回来源的名字。
接下来为项目类型 FixedBilling
定义一个结构体。
type FixedBilling struct {
projectName string
biddedAmount int
}
FixedBilling
项目有两个字段,projectName
代表了项目的名字,biddedAmount
为组织为项目出价的金额。
TimeAndMaterial
结构体代表项目的时间和材料类型。
type TimeAndMaterial struct {
projectName string
noOfHours int
hourlyRate int
}
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 = 0
for _, 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
。然后,我们使用三个项目,创建一个类型为 Income
的切片。由于每个工程都已经实现了 Income
接口,可以把三个项目全部加入了类型为 Income
的切片中。最后我们使用这个切片调用 calculateNetIncomde
函数,它将打印多个收入来源和它们的收入。
这 下面整个程序供您参考
package main
import (
"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 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 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Net income of organisation = $19000
为上面的程序增加一个新的收入流
程序通过广告发现了一个新的收入流。我们来看添加一个新的收入流并计算总收入是多么的简单,不需要calculateNetIncome
函数的任何代码。这由于多态的成为了可能。
我们首先定义一个 Advertisement
类型,并在该类型上定义 calculate
和 source()
方法。
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
}
类型 Advertisment
有三个字段:adName
,CPC
(每次点击的花费Cost Per Click) 和 noOfClicks
(点击数 Number of Clicks)。ads的总收入是产品的 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
和 popuAd
广告。切片 incomesStreams
包含这两个我们刚创建的广告。
下面是增加广告后的全程序
package main
import (
"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 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 = $5000
Income From Project 2 = $10000
Income From Project 3 = $4000
Income From Banner Ad = $1000
Income From Popup Ad = $3750
Net income of organisation = $23750
你会注意到我们尽管增加了新的收入流,而并没有对 calculateNetIncomde
函数做任何修改。由于多态性可以工作。由于新的 Advertisement
类型也实现了 Incomde
接口,我们可以将它加入了 incomeStreams
切片中。calculateNetIncomdes
函数不需要任何修改也可以工作,因为它可以调用 Advertisement
类型的 calculate()
和 source()
方法。
下一教程 - Defer