『就要學習 Go 語言』系列 -- 第 26 篇分享好文html
今天接着給你們分享關於 Go 面向對象的好文。原文做者是 William Kennedy,《Go 語言實戰》做者,博客 www.ardanlabs.com/blog/ 的維護者。大部分中國的 Gopher 都是經過這個博客認識了這位 Go 大神。golang
有些與知識點無關的語句,翻譯過來有點拗口,你們一眼帶過。但與知識點相關的,都會盡可能忠於原文。另外,文章作了簡單的排版,方便閱讀。翻譯水平有限,有誤的地方,請你們在下方留言指正。編程
今天有人在論壇上問我一個問題,如何在不經過嵌入的狀況下而得到繼承的優勢。重要的是,每一個人都應該考慮 Go 語言,而不是他們留下的語言。我不能告訴你我從早期的 Go 實現中刪除了多少代碼,由於這是沒必要要的。語言設計師有多年的經驗和知識,正在幫助建立一種快速、精簡且編寫代碼很是有趣的語言。編程語言
我認爲 Go 是一門輕量級的面向對象編程語言。是的,它具備封裝和類型成員函數,但它缺少繼承,所以缺少傳統的多態性。對我來講,除非想實現多態性,不然繼承是無用的。經過在 Go 中實現接口的方式,不須要繼承。Go採用 OOP 中最好的部分,而忽略了其餘部分,爲咱們提供了一種編寫多態代碼的更好方法。函數
下面是 Go 中的 OOP 快速概覽。先從這三個結構體開始。oop
type Animal struct {
Name string
mean bool
}
type Cat struct {
Basics Animal
MeowStrength int
}
type Dog struct {
Animal
BarkStrength int
}
複製代碼
在任何關於 OOP 的示例中,你均可能看到上面三個結構體。一個基礎結構體 Animal 和基於 Animal 聲明的結構體 Cat 和 Dog。結構體 Animal 擁有全部動物共同的屬性。學習
除了成員 mean,其餘全部成員都是公共的、可被外部訪問的。結構體 Animal 的 mean 成員以小寫字母開頭。在 Go 中,變量、結構體、成員、函數等的第一個字母的大小寫決定了訪問權限。大寫字母開頭表示公共的,可供外部調用;小寫字母開頭表示私有的,外部不能調用。ui
因爲 Go 裏沒有繼承,因此組合是你惟一的選擇。結構體 Cat 的一個成員是 Basics,類型是 Animal。而結構體 Dog 經過匿名的方式嵌入告終構體 Animal。哪一種實現方式更好取決於你,我會展現這兩種實現方式。spa
給結構體 Cat 和 Dog 建立各自的方法:.net
func (dog *Dog) MakeNoise() {
barkStrength := dog.BarkStrength
if dog.mean == true {
barkStrength = barkStrength * 5
}
for bark := 0; bark < barkStrength; bark++ {
fmt.Printf("BARK ")
}
fmt.Println("")
}
func (cat *Cat) MakeNoise() {
meowStrength := cat.MeowStrength
if cat.Basics.mean == true {
meowStrength = meowStrength * 5
}
for meow := 0; meow < meowStrength; meow++ {
fmt.Printf("MEOW ")
}
fmt.Println("")
}
複製代碼
使用指針接收者實現各自的方法 MakeNoise()。這兩個方法作一樣的事情,若是 mean 爲 true 話,每一個動物會基於吠或喵的強度,用各自的母語說話。很無聊,但它展現瞭如何訪問引用的對象。
咱們可使用 Dog 引用直接調用結構體 Animal 的成員,而 Cat 必須經過成員 Basics 訪問到 Animal 的成員。
到目前爲止,咱們已經討論了封裝、組合、訪問規範和成員函數,剩下的就是如何建立多態行爲。
經過接口實現多態:
type AnimalSounder interface {
MakeNoise()
}
func MakeSomeNoise(animalSounder AnimalSounder) {
animalSounder.MakeNoise()
}
複製代碼
上面的代碼,咱們添加一個接口 AnimalSounder 和一個公共函數 MakeSomeNoise(),該函數接受接口類型的值。實際上,該函數將引用實現此接口的類型的值。接口不是能夠實例化的類型,是行爲聲明,其餘類型能夠去實現接口聲明的行爲。
在 Go 中,經過方法實現接口的任何類型都表示接口類型。在咱們的例子中,結構體 Dog 和 Cat 都經過指針接收者實現了接口 AnimalSounder,因此它們均可以當作是 AnimalSounder 類型。
這意味着,Dog 和 Cat 的指針能夠做爲參數傳遞給函數 MakeSomeNoise()。MakeSomeNoise() 函數經過 AnimalSounder 接口實現多態行爲。
若是你想減小 Cat 和 Dog 的 MakeNoise() 方法中的代碼重複,能夠爲 Animal 類型建立一個方法來處理:
func (animal *Animal) PerformNoise(strength int, sound string) {
if animal.mean == true {
strength = strength * 5
}
for voice := 0; voice < strength; voice++ {
fmt.Printf("%s ", sound)
}
fmt.Println("")
}
func (dog *Dog) MakeNoise() {
dog.PerformNoise(dog.BarkStrength, "BARK")
}
func (cat *Cat) MakeNoise() {
cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}
複製代碼
如今 Animal 類型有一個處理 noise 的方法,能夠被其外部類型調用,例如 Dog、Cat 類型。還有一個好處,咱們不須要將 mean 成員做爲參數傳遞,由於它就屬於 Animal 結構體。 下面是完整代碼:
package main
import (
"fmt"
)
type Animal struct {
Name string
mean bool
}
type AnimalSounder interface {
MakeNoise()
}
type Dog struct {
Animal
BarkStrength int
}
type Cat struct {
Basics Animal
MeowStrength int
}
func main() {
myDog := &Dog{
Animal{
"Rover", // Name
false, // mean
},
2, // BarkStrength
}
myCat := &Cat{
Basics: Animal{
Name: "Julius",
mean: true,
},
MeowStrength: 3,
}
MakeSomeNoise(myDog)
MakeSomeNoise(myCat)
}
func (animal *Animal) PerformNoise(strength int, sound string) {
if animal.mean == true {
strength = strength * 5
}
for voice := 0; voice < strength; voice++ {
fmt.Printf("%s ", sound)
}
fmt.Println("")
}
func (dog *Dog) MakeNoise() {
dog.PerformNoise(dog.BarkStrength, "BARK")
}
func (cat *Cat) MakeNoise() {
cat.Basics.PerformNoise(cat.MeowStrength, "MEOW")
}
func MakeSomeNoise(animalSounder AnimalSounder) {
animalSounder.MakeNoise()
}
複製代碼
輸出:
BARK BARK
MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW
複製代碼
有人在後臺給出了一個在結構體中嵌入接口的例子:
package main
import (
"fmt"
)
type HornSounder interface {
SoundHorn()
}
type Vehicle struct {
List [2]HornSounder
}
type Car struct {
Sound string
}
type Bike struct {
Sound string
}
func main() {
vehicle := new(Vehicle)
vehicle.List[0] = &Car{"BEEP"}
vehicle.List[1] = &Bike{"RING"}
for _, hornSounder := range vehicle.List {
hornSounder.SoundHorn()
// PressHorn(hornSounder) 這種方式也能夠
}
}
func (car *Car) SoundHorn() {
fmt.Println(car.Sound)
}
func (bike *Bike) SoundHorn() {
fmt.Println(bike.Sound)
}
func PressHorn(hornSounder HornSounder) {
hornSounder.SoundHorn()
}
複製代碼
在這個例子中,結構體 Vehicle 維護了一個實現 HornSounder 接口的值列表。在 main 函數中建立了變量 vehicle,並存儲了 Car 類型變量和 Bike 變量的指針。這種賦值操做是能夠的,由於 Car 和 Bike 都實現了接口。接着就是要一個簡單的 loop 操做,循環調用 SoundHorn() 方法。
在你的應用程序中,任何你須要實現面向對象的東西在 Go 語言中都有。正如我以前所說的,Go 採用了 OOP 中最好的部分,省略了其餘部分,爲咱們提供了編寫多態代碼的更好方法。
與主題相關聯的幾篇文章:
1.Methods, Interfaces and Embedded Types in Go
2.How Packages Work in Go
3.Singleton Design Pattern in Go
但願這幾個簡單的例子能對你的 Go 編程有幫助!
推薦閱讀:
1.教女友寫方法(續)
2.Go 面向對象式編程