Part28 Go的面向对象 - 多态

欢迎来到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 结构体有三个名为 projectNamenoOfHourshourlyRate 的字段 。

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

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。这个值从接收器类型 TimeAndMaterial 的方法 calculate() 返回。

我们从 source() 方法中返回源收入的项目名字。

由于 FixedBillingTimeAndMaterial 结构体都提供了接口 Incomecalculatesource 方法的定义,这两上结构体都实现了 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 类型,并在该类型上定义 calculatesource() 方法。

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 有三个字段:adNameCPC(每次点击的花费Cost Per Click) 和 noOfClicks(点击数 Number of Clicks)。ads的总收入是产品的 CPCNoOfClicks

我们修改一点 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)
}

我们创建了两个名为 bannerAdpopuAd 广告。切片 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值