当前位置: 首页 > Go语言, 网络翻译 > 正文

Go语言中的面向对象编程

本文为技术翻译,原文出自:《Object Oriented Programming in Go》 。

今天有人在论坛上提出了一个问题,即如何在不嵌入的情况下获得继承的好处。对于 每个团队中的人来说,考虑Go的应用而不是他们留下的代码这是非常重要的。 我无法告诉你我在早期Go项目实现中删除的那些代码,因为它没有必要。 Go语言设计者们拥有多年的经验和知识。 Hindsight正在帮助创建一种快速,精简且非常有趣的语言。

我认为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模式的Go代码中看到的三个结构。 我们定义了一个基础结构体和两个特定于基础结构体的结构体。Animal结构体包含所有Animals共享的属性,另外两个结构体则特定于Cat和Dog。

除了mean之外,所有成员属性(字段)都是公共的。 Animal结构中的mean字段以小写字母开头。 在Go中,变量,结构,字段,函数等的第一个字母的大小写确定了其可访问的规范。 使用大写字母,它是公开的(public),使用小写字母,它是私人的(private)。

http://www.goinggo.net/2014/03/exportedunexported-identifiers-in-go.html

由于Go中没有继承,因此组合是你唯一的选择。 Cat结构有一个名为Basics的字段,其类型为Animal。 Dog结构使用未命名的struct(嵌入)作为Animal类型。 由你来决定哪个更适合。

要为Cat和Dog创建成员函数(方法),语法如下:

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("")
}

在方法名称之前,我们指定了一个接收器,它是指向每种类型的指针。 现在Cat和Dog都可以调用MakeNoise方法。

它们的MakeNoise方法都做同样的事情。 每个Animal都会根据它们的Cat或Dog的MakeNoise方法打印一些字符,如果mean为true的话。它告诉我们如何访问引用的对象(值)。

使用Dog参考时,我们直接访问Animal字段。 使用Cat参考,我们使用名为Basics的命名字段。

现在我们已经介绍了封装,组合,访问规范和成员函数, 剩下的就是如何创建多态行为。

我们使用接口来创建多态行为:

type AnimalSounder interface {
    MakeNoise()
}

func MakeSomeNoise(animalSounder AnimalSounder) {
    animalSounder.MakeNoise()
}

上面我们添加了一个接口和一个公共函数,它接受一个接口类型的值。 实际上,该函数将引用实现此接口的类型的值。 接口不是可以实例化的类型。 接口是由其他类型实现的行为声明。

当接口只包含一个方法时,Go中约定通常使用“er”后缀命名接口。

在Go中,通过方法实现接口的任何类型都表示接口类型。 在我们的例子中,Cat和Dog都使用指针接收器实现了AnimalSounder接口,因此被认为是AnimalSounder类型。

这意味着Cat和Dog的指针都可以作为参数传递给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类型有了一个包含调用MakeNoise业务逻辑的处理方法。这里的另一个好处是我们不需要将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()
    }
}

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实例,并为其成员变量List初始化了Car和Bike实例。 这种赋值是允许的,因为Car和Bike存在Sound。 然后使用一个简单的循环,使用hornSounder接口来SoundHorn。

如需学习更多相关知识,可以参考下面链接:

http://www.goinggo.net/2014/05/methods-interfaces-and-embedded-types.html

http://www.goinggo.net/2013/07/how-packages-work-in-go-language.html

http://www.goinggo.net/2013/07/singleton-design-pattern-in-go.html



这篇博文由 s0nnet 于2018年08月05日发表在 Go语言, 网络翻译 分类下, 通告目前不可用,你可以至底部留下评论。
如无特别说明,计算机技术分享发表的文章均为原创,欢迎大家转载,转载请注明: Go语言中的面向对象编程 | 计算机技术分享
关键字: ,

Go语言中的面向对象编程:等您坐沙发呢!

发表评论

快捷键:Ctrl+Enter