Go 面向对象编程(译)

『就要学习 Go 语言』系列 -- 第 26 篇分享好文html

今天接着给你们分享关于 Go 面向对象的好文。原文做者是 William Kennedy,《Go 语言实战》做者,博客 www.ardanlabs.com/blog/ 的维护者。大部分中国的 Gopher 都是经过这个博客认识了这位 Go 大神。golang

有些与知识点无关的语句,翻译过来有点拗口,你们一眼带过。但与知识点相关的,都会尽可能忠于原文。另外,文章作了简单的排版,方便阅读。翻译水平有限,有误的地方,请你们在下方留言指正。编程

基础

今天有人在论坛上问我一个问题,如何在不经过嵌入的状况下而得到继承的优势。重要的是,每一个人都应该考虑 Go 语言,而不是他们留下的语言。我不能告诉你我从早期的 Go 实现中删除了多少代码,由于这是没必要要的。语言设计师有多年的经验和知识,正在帮助建立一种快速、精简且编写代码很是有趣的语言。markdown

我认为 Go 是一门轻量级的面向对象编程语言。是的,它具备封装和类型成员函数,但它缺少继承,所以缺少传统的多态性。对我来讲,除非想实现多态性,不然继承是无用的。经过在 Go 中实现接口的方式,不须要继承。Go采用 OOP 中最好的部分,而忽略了其余部分,为咱们提供了一种编写多态代码的更好方法。编程语言

下面是 Go 中的 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 拥有全部动物共同的属性。oop

除了成员 mean,其余全部成员都是公共的、可被外部访问的。结构体 Animal 的 mean 成员以小写字母开头。在 Go 中,变量、结构体、成员、函数等的第一个字母的大小写决定了访问权限。大写字母开头表示公共的,可供外部调用;小写字母开头表示私有的,外部不能调用。学习

因为 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 面向对象式编程