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