本文译自Steve Francia在OSCON 2014的一个PPT,原作请前往:https://spf13.com/presentation/go-for-object-oriented-programmers/
对我来说,最吸引我的不是Go拥有的特征,而是那些被故意遗漏的特征。 —— txxxxd
为什么你要创造一种从理论上来说,并不令人兴奋的语言?
因为它非常有用。 —— Rob Pike
Go中的“对象”
要探讨Go语言中的对象,我们先搞清楚一个问题:
Go语言有对象吗?
从语法上来说,
- Go中没有类(Classes)
- Go中没有“对象”(Objects)
到底什么是对象?
对象是一种抽象的数据类型,拥有状态(数据)和行为(代码)。 —— Steve Francia
在Go语言中,我们这样声明一个类型:
类型声明(Struct)
type Rect struct {
width int
height int
}
然后我们可以给这个Struct声明一个方法
func (r *Rect) Area() int {
return r.width * r.height
}
用起来就像这样
func main() {
r := Rect{width: 10, height: 5}
fmt.Println("area: ", r.Area())
}
我们不光可以声明结构体类型,我们可以声明任何类型。比如一个切片:
类型声明(Slice)
type Rects []*Rect
同样也可以给这个类型声明一个方法
func (rs Rects) Area() int {
var a int
for _, r := range rs {
a += r.Area()
}
return a
}
用起来
func main() {
r := &Rect{width: 10, height: 5}
x := &Rect{width: 7, height: 10}
rs := Rects{r, x}
fmt.Println("r's area: ", r.Area())
fmt.Println("x's area: ", x.Area())
fmt.Println("total area: ", rs.Area())
}
https://play.golang.org/p/G1OWXPGvc3
我们甚至可以声明一个函数类型
类型声明(Func)
type Foo func() int
同样的,给这个(函数)类型声明一个方法
func (f Foo) Add(x int) int {
return f() + x
}
然后用起来
func main() {
var x Foo
x = func() int { return 1 }
fmt.Println(x())
fmt.Println(x.Add(3))
}
https://play.golang.org/p/YGrdCG3SlI
通过上边的例子,这样看来,其实
Go有“对象”
那么我们来看看
“面向对象”的Go
如果一种语言包含对象的基本功能:标识、属性和特性,则通常认为它是基于对象的。
如果一种语言是基于对象的,并且具有多态性和继承性,那么它被认为是面向对象的。 —— Wikipedia
第一条,我们在上边的例子看到了,go中的type declaration其实满足了Go语言是基于对象的。那么,
Go是基于对象的,它是面向对象的吗?
我们来看看关于第二条,继承性和多态性
继承
- 提供对象的复用
- 类是按层级创建的
- 继承允许一个类中的结构和方法向下传递这种层级
Go中实现继承的方式
- Go明确地避免了继承
- Go严格地遵循了符合继承原则的组合方式
- Go中通过嵌入类型来实现组合
组合
- 提供对象的复用
- 通过包含其他的对象来声明一个对象
- 组合使一个类中的结构和方法被拉进其他类中
继承把“知识”向下传递,组合把“知识”向上拉升 —— Steve Francia
嵌入类型
type Person struct {
Name string
Address
}
type Address struct {
Number string
Street string
City string
State string
Zip string
}
给被嵌入的类型声明一个方法
func (a *Address) String() string {
return a.Number + " " + a.Street + "\n" + a.City + ", " + a.State + " " + a.Zip + "\n"
}
使用组合字面量声明一个Struct
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
}
跑起来试试
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
fmt.Println(p.String())
}
https://play.golang.org/p/9beVY9jNlW
升级
- 升级会检查一个内部类型是否能满足需要,并“升级”它
- 内嵌的数据域和方法会被“升级”
- 升级发生在运行时而不是声明时
- 被升级的方法被认为是符合接口的
升级不是重载
func (a *Address) String() string {
return a.Number + " " + a.Street + "\n" + a.City + ", " + a.State + " " + a.Zip + "\n"
}
func (p *Person) String() string {
return p.Name + "\n" + p.Address.String()
}
外部结构的方法和内部结构的方法都是可见的
func main() {
p := Person{
Name: "Steve",
Address: Address{
Number: "13",
Street: "Main",
City: "Gotham",
State: "NY",
Zip: "01313",
},
}
fmt.Println(p.String())
fmt.Println(p.Address.String())
}