[譯] part 28: golang 的物件導向 -- 多型

咔嘰咔嘰發表於2019-04-12

Go 中的多型性是在介面的幫助下實現的。正如我們已經討論過的,介面可以在 Go 中隱式實現。如果型別為介面中宣告的所有方法提供定義,則型別實現介面。讓我們看看在介面的幫助下如何在 Go 中實現多型性。

使用介面實現多型

任何定義了介面的所有方法的型別都被稱為隱式實現了該介面。

一個介面型別的變數可以承載任何實現該介面的值。介面的這個屬性用於在 Go 中實現多型。

讓我們在一個計算組織淨收入的程式的幫助下來理解 Go 中的多型性。為簡單起見,我們假設這個想象中的組織有兩種專案的收入,即固定賬單,時間和材料。該組織的淨收入按這些專案的收入總和計算。為了簡化本教程,我們假設貨幣是美元,我們不會處理美分。它將使用int表示。 (我建議閱讀該文章以瞭解如何表示美分。感謝 Andreas Matuschek 在評論部分指出了這一點。)

我們定義一個Income結構,

type Income interface {  
    calculate() int
    source() string
}
複製程式碼

上面定義的Income介面包含兩個方法calculate(),它計算並返回專案來源收入的和,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的乘積。返回值來自於使用接收者型別為TimeAndMaterialcalculate()方法。

我們用source()方法來返回收入來源專案的名字。

由於FixedBillingTimeAndMaterial結構都為Income介面的calculate()source()方法提供了定義,因此兩個結構都實現了Income介面。

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函式中實現了多型性。

在將來,如果新增了一種新的收入來源,這個功能仍然可以正確計算總收入而無需一行程式碼更改:)。

接下來我們看看 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}
    incomeStreams := []Income{project1, project2, project3}
    calculateNetIncome(incomeStreams)
}
複製程式碼

在上面的 main 函式中,我們建立了三個專案,兩個型別為FixedBilling,另一個型別為TimeAndMaterial。接下來,我們使用這 3 個專案建立一個型別為Income的切片。由於這些專案中的每一個都實現了Income介面,因此可以將所有三個專案新增到一個型別為Income的片段中。最後,我們用這個切片呼叫calculateNetIncome函式,它將顯示各種收入和收入來源。

整個程式如下,

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)
}
複製程式碼

Run in playground

程式輸出,

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
}
複製程式碼

Advertisement型別有三個欄位:adNameCPC(每次點選費用)和noOfClicks(點選次數)。廣告的總收入是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)
}
複製程式碼

我們建立了兩個名為bannerAdpopupAd的廣告收入。 incomeStreams切片包含我們剛剛建立的兩個廣告。

加上後的整個程式碼如下,

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)
}
複製程式碼

Run in playground

上述程式輸出,

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
複製程式碼

您會注意到雖然我們新增了新的收入來源,但我們沒有對calculateNetIncome函式進行任何更改。這就是多型性的作用。由於新的Advertisement型別也實現了Income介面,我們可以將它新增到incomeStreams切片中。 calculateNetIncome函式也沒有任何變化,因為它能夠呼叫Advertisement型別的calculate()source()方法。

相關文章