Go 面向對象編程(譯)

『就要學習 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 面向對象式編程

相關文章
相關標籤/搜索