go(基础08)——多态

1. 使用接口实现多态

一个类型如果定义了接口的所有方法,那它就隐式地实现了该接口。

所有实现了接口的类型,都可以把它的值保存在一个接口类型的变量中。在 Go 中,我们使用接口的这种特性来实现多态。

通过一个程序我们来理解 Go 语言的多态,它会计算一个组织机构的净收益。为了简单起见,我们假设这个虚构的组织所获得的收入来源于两个项目:fixed billing 和 time and material。该组织的净收益等于这两个项目的收入总和。同样为了简单起见,我们假设货币单位是美元,而无需处理美分。因此货币只需简单地用 int 来表示。我们首先定义一个接口 Income。

Go 复制代码
type Income interface {
    calculate() int
    source() string
}

上面定义了接口 Interface,它包含了两个方法:calculate() 计算并返回项目的收入,而 source() 返回项目名称。下面我们定义一个表示 FixedBilling 项目的结构体类型。

Go 复制代码
type FixedBilling struct {
    projectName string
    biddedAmount int
}

项目 FixedBillin 有两个字段:projectName 表示项目名称,而 biddedAmount 表示组织向该项目投标的金额。TimeAndMaterial 结构体用于表示项目 Time and Material。

Go 复制代码
type TimeAndMaterial struct {
    projectName string
    noOfHours  int
    hourlyRate int
}

结构体 TimeAndMaterial 拥有三个字段名:projectName、noOfHours 和 hourlyRate。

下一步我们给这些结构体类型定义方法,计算并返回实际收入和项目名称。

Go 复制代码
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 函数,用来计算并打印总收入。

Go 复制代码
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)
}

上面的函数接收一个 Income 接口类型的切片作为参数。该函数会遍历这个接口切片,并依个调用 calculate() 方法,计算出总收入。该函数同样也会通过调用 source() 显示收入来源。根据 Income 接口的具体类型,程序会调用不同的 calculate() 和 source() 方法。于是,我们在 calculateNetIncome 函数中就实现了多态。

如果在该组织以后增加了新的收入来源,calculateNetIncome 无需修改一行代码,就可以正确地计算总收入了。最后就剩下这个程序的 main 函数了。

Go 复制代码
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 类型的切片,存放了这三个项目。由于这三个项目都实现了 Interface 接口,因此可以把这三个项目放入 Income 切片。最后我们将该切片作为参数,调用了 calculateNetIncome 函数,显示了项目不同的收益和收入来源。

完整代码如下:

Go 复制代码
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)
}

打印结果:

2. 新增收益流

假设前面的组织通过广告业务,建立了一个新的收益流(Income Stream)。我们可以看到添加它非常简单,并且计算总收益也很容易,我们无需对 calculateNetIncome 函数进行任何修改。这就是多态的好处。我们首先定义 Advertisement 类型,并在 Advertisement 类型中定义 calculate() 和 source() 方法。

Go 复制代码
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 函数,把新的收益流添加进来。

Go 复制代码
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。incomeStream 切片包含了这两个创建的广告项目。

Go 复制代码
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)
}

打印结果:、

相关推荐
wenyq721 小时前
LeetCode 2460. Apply Operations to an Array
算法·leetcode
.格子衫.1 天前
032动态规划之区间DP——算法备赛
算法·动态规划
不会代码的小猴1 天前
7. JSON
开发语言·c++·笔记·qt·算法·json
怪奇云呼军1 天前
知识库也会注入指令?闪电智能VoiceAgent 如何防住 Prompt Injection
人工智能·python·算法·云计算·音视频
阿里云大数据AI技术1 天前
基于 EMR Serverless Ray 实现 Qwen 模型批量推理实践
人工智能·算法·agent
ttwuai1 天前
Go 后台接入 SSO 后菜单正常但接口 403,怎么排查权限链路?
开发语言·后端·golang
Rambo.xia1 天前
为什么去马赛克算法,决定了ISP的画质上限
算法·接口隔离原则
PC2005-cloud1 天前
Go学习笔记:基本概念与项目结构——GOPATH、Go Modules 与常用命令
笔记·学习·golang
Benny_Tang1 天前
题解:P10230 [COCI 2023/2024 #4] Lepeze
c++·算法