多态性
Q7nl1s admin

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结构有三个字段:projectNamenoOfHourshourlyRate

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

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项目中,收入是noOfHourshourlyRate的乘积。这个值从接收器类型为TimeAndMaterialcalculate()方法中返回。

我们从source()方法中返回项目的名称作为收入的来源。

由于FixedBillingTimeAndMaterial结构都提供了收入接口的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 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 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类型有三个字段adNameCPC(每次点击费用)和noOfClicks(点击次数)。广告的总收入是CPCnoOfClicks的乘积。

让我们稍微修改一下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)
}

我们已经创建了两个广告,即bannerAdpopupAdincomeStreams片断包括我们刚刚创建的两个广告。

下面是添加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 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 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()方法。

这样我们就到了本教程的结尾。祝你有个愉快的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View