這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。歡迎來到 [Golang 系列教程](https://studygolang.com/subject/2)的第 28 篇。Go 通過[介面](https://studygolang.com/articles/12266)來實現多態。我們已經討論過,在 Go 語言中,我們是隱式地實現介面。一個類型如果定義了介面所聲明的全部[方法](https://studygolang.com/articles/12264),那它就實現了該介面。現在我們來看看,利用介面,Go 是如何?多態的。## 使用介面實現多態一個類型如果定義了介面的所有方法,那它就隱式地實現了該介面。**所有實現了介面的類型,都可以把它的值儲存在一個介面類型的變數中。在 Go 中,我們使用介面的這種特性來實現多態**。通過一個程式我們來理解 Go 語言的多態,它會計算一個組織機構的淨收益。為了簡單起見,我們假設這個虛構的組織所獲得的收入來源於兩個項目:`fixed billing` 和 `time and material`。該組織的淨收益等於這兩個項目的收入總和。同樣為了簡單起見,我們假設貨幣單位是美元,而無需處理美分。因此貨幣只需簡單地用 `int` 來表示。(我建議閱讀 https://forum.golangbridge.org/t/what-is-the-proper-golang-equivalent-to-decimal-when-dealing-with-money/413 上的文章,學習如何表示美分。感謝 Andreas Matuschek 在評論區指出這一點。)我們首先定義一個介面 `Income`。```gotype Income interface { calculate() int source() string}```上面定義了介面 `Interface`,它包含了兩個方法:`calculate()` 計算並返回項目的收入,而 `source()` 返回項目名稱。下面我們定義一個表示 `FixedBilling` 項目的結構體類型。```gotype FixedBilling struct { projectName string biddedAmount int}```項目 `FixedBillin` 有兩個欄位:`projectName` 表示項目名稱,而 `biddedAmount` 表示組織向該項目投標的金額。`TimeAndMaterial` 結構體用於表示項目 Time and Material。```gotype TimeAndMaterial struct { projectName string noOfHours int hourlyRate int}```結構體 `TimeAndMaterial` 擁有三個欄位名:`projectName`、`noOfHours` 和 `hourlyRate`。下一步我們給這些結構體類型定義方法,計算並返回實際收入和項目名稱。```gofunc (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` 函數,用來計算並列印總收入。```gofunc 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)}```上面的[函數](https://studygolang.com/articles/11892)接收一個 `Income` 介面類型的[切片](https://studygolang.com/articles/12121)作為參數。該函數會遍曆這個介面切片,並依個調用 `calculate()` 方法,計算出總收入。該函數同樣也會通過調用 `source()` 顯示收入來源。根據 `Income` 介面的具體類型,程式會調用不同的 `calculate()` 和 `source()` 方法。於是,我們在 `calculateNetIncome` 函數中就實現了多態。如果在該組織以後增加了新的收入來源,`calculateNetIncome` 無需修改一行代碼,就可以正確地計算總收入了。:)最後就剩下這個程式的 `main` 函數了。```gofunc 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` 函數,顯示了項目不同的收益和收入來源。以下完整的代碼供你參考。```gopackage mainimport ( "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)}```[在 playground 上運行](https://play.golang.org/p/UClAagvLFT)該程式會輸出:```Income From Project 1 = $5000 Income From Project 2 = $10000 Income From Project 3 = $4000 Net income of organisation = $19000 ```## 新增收益流假設前面的組織通過廣告業務,建立了一個新的收益流(Income Stream)。我們可以看到添加它非常簡單,並且計算總收益也很容易,我們無需對 `calculateNetIncome` 函數進行任何修改。這就是多態的好處。我們首先定義 `Advertisement` 類型,並在 `Advertisement` 類型中定義 `calculate()` 和 `source()` 方法。```gotype 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` 函數,把新的收益流添加進來。```gofunc 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` 切片包含了這兩個建立的廣告項目。```gopackage mainimport ( "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)}```[在 playground 中運行](https://play.golang.org/p/BYRYGjSxFN)上面程式會輸出:```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` 切片添加 `Advertisement`。`calculateNetIncome` 無需修改,因為它能夠調用 `Advertisement` 類型的 `calculate()` 和 `source()` 方法。本教程到此結束。祝你愉快。**上一教程 - [組合取代繼承](https://studygolang.com/articles/12680)****下一教程 - [Defer](https://studygolang.com/articles/12719)**
via: https://golangbot.com/polymorphism/
作者:Nick Coghlan 譯者:Noluye 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1825 次點擊