处理 Go 中缺乏继承的最佳实践
Best practices to deal with the lack of inheritance in Go
我正要开始编写一个新的应用程序,我正在考虑用 Go 编程,但我没有这方面的经验。由于 Go 不支持继承,我如何忠实地将这样的域翻译成 Go,同时仍然符合 Go 的哲学?:
- 一个人有2条腿和2条胳膊,可以走路。
- 音乐家是一个人,有乐器可以演奏,有分区可以读。但是没有人只能是音乐家,因为音乐家拥有并会演奏特定的乐器。
- 小提琴手是拥有小提琴并且会拉小提琴的音乐家
- 钢琴家是拥有钢琴并会弹奏钢琴的音乐家。
- 一个violonist/pianist走在大街上,大家都只把他当成一个人
也许我们可以像这样重新构造域以删除任何 'is a' 关系,以便它可以翻译成 Go:
- 小提琴手有人物部分,音乐家部分,一把小提琴,可以拉小提琴
- violinist/pianist 的音乐家部分有分区并且可以读取它们
- 一个violinist/pianist走在大街上,每个人都只能看到他自己的部分
- 等等
在翻译成 Go 之前,像这样对这样的域建模是最佳实践吗?当 Go 的作者决定在 Go 中排除继承时,是否是 Go 的作者希望他们的用户拥有的思维方式?
不是"the way of thinking that the authors of Go want their users to have"; Go 没有继承,因为它不是面向对象的语言,就像 C 没有继承一样。
它确实有组合和接口,这是您对所描述的内容进行建模所需要的全部:一个对象 "has a" 其他对象应该具有该类型(组合)的字段,以及一个对象"can" 执行一些可以通过接口建模的操作。
但是,如果这个问题描述的是一个实际的编程问题而不是一个隐喻,那么它可能更容易回答;当然,除非您的应用程序打算模拟各种音乐家的肢体和乐器。
围棋有另一种观点。你可以想到它们匹配的对象和接口。第二句离你更近了:
A violinist has a person part, a musician part, a violin and can play the violin
这是接口 Musician
- 它描述了任何拥有方法的人 Play()
type Musician interface {
Play()
}
例如结构Violinist
可以有这样的方法:
type Violinist struct {}
func (v Violinist) Play() {}
也Pianist
可以玩:
type Pianist struct {}
func (v Pianist) Play() {}
它们都匹配接口Musician
。所以你可以有一个 musician
变量并将不同的音乐家分配给它:
var musician Musician
musician = Violinist{}
musician.Play()
musician = Pianist{}
musician.Play()
看看同一个变量如何改变它的行为。它和 Polyformism 一样,不是 OOP-way,而是 Go-way。
When a violinist/pianist is walking in the streets, everybody can only see his person part
我们可以用同样的方式定义一个 Walker
接口 - 可以走在街上并有适当的方法 Walk
的人:
type Walker interface {
Walk()
}
这是一种鸭子打字:在我们的生活中,如果一个人可以演奏,就可以带到合奏中。在 Go 术语中,如果一个人有方法 Play()
,它可以分配给音乐家类型变量。
Etc
接下来你可以制作一个嵌入这两者的组合界面:
type WalkingMusician interface {
Musician
Walker
}
形容一个人瞬间会玩会走路
var walkingMusician WalkingMusician
walkingMusician = walkingViolinist
walkingMusician.Walk()
walkingMusician.Play()
我正要开始编写一个新的应用程序,我正在考虑用 Go 编程,但我没有这方面的经验。由于 Go 不支持继承,我如何忠实地将这样的域翻译成 Go,同时仍然符合 Go 的哲学?:
- 一个人有2条腿和2条胳膊,可以走路。
- 音乐家是一个人,有乐器可以演奏,有分区可以读。但是没有人只能是音乐家,因为音乐家拥有并会演奏特定的乐器。
- 小提琴手是拥有小提琴并且会拉小提琴的音乐家
- 钢琴家是拥有钢琴并会弹奏钢琴的音乐家。
- 一个violonist/pianist走在大街上,大家都只把他当成一个人
也许我们可以像这样重新构造域以删除任何 'is a' 关系,以便它可以翻译成 Go:
- 小提琴手有人物部分,音乐家部分,一把小提琴,可以拉小提琴
- violinist/pianist 的音乐家部分有分区并且可以读取它们
- 一个violinist/pianist走在大街上,每个人都只能看到他自己的部分
- 等等
在翻译成 Go 之前,像这样对这样的域建模是最佳实践吗?当 Go 的作者决定在 Go 中排除继承时,是否是 Go 的作者希望他们的用户拥有的思维方式?
不是"the way of thinking that the authors of Go want their users to have"; Go 没有继承,因为它不是面向对象的语言,就像 C 没有继承一样。
它确实有组合和接口,这是您对所描述的内容进行建模所需要的全部:一个对象 "has a" 其他对象应该具有该类型(组合)的字段,以及一个对象"can" 执行一些可以通过接口建模的操作。
但是,如果这个问题描述的是一个实际的编程问题而不是一个隐喻,那么它可能更容易回答;当然,除非您的应用程序打算模拟各种音乐家的肢体和乐器。
围棋有另一种观点。你可以想到它们匹配的对象和接口。第二句离你更近了:
A violinist has a person part, a musician part, a violin and can play the violin
这是接口 Musician
- 它描述了任何拥有方法的人 Play()
type Musician interface {
Play()
}
例如结构Violinist
可以有这样的方法:
type Violinist struct {}
func (v Violinist) Play() {}
也Pianist
可以玩:
type Pianist struct {}
func (v Pianist) Play() {}
它们都匹配接口Musician
。所以你可以有一个 musician
变量并将不同的音乐家分配给它:
var musician Musician
musician = Violinist{}
musician.Play()
musician = Pianist{}
musician.Play()
看看同一个变量如何改变它的行为。它和 Polyformism 一样,不是 OOP-way,而是 Go-way。
When a violinist/pianist is walking in the streets, everybody can only see his person part
我们可以用同样的方式定义一个 Walker
接口 - 可以走在街上并有适当的方法 Walk
的人:
type Walker interface {
Walk()
}
这是一种鸭子打字:在我们的生活中,如果一个人可以演奏,就可以带到合奏中。在 Go 术语中,如果一个人有方法 Play()
,它可以分配给音乐家类型变量。
Etc
接下来你可以制作一个嵌入这两者的组合界面:
type WalkingMusician interface {
Musician
Walker
}
形容一个人瞬间会玩会走路
var walkingMusician WalkingMusician
walkingMusician = walkingViolinist
walkingMusician.Walk()
walkingMusician.Play()