英文:
What is the best way to model a "superclass method implementation" in Go?
问题
我正在寻找将一个“经典的面向对象”示例转换为Go语言的方法,其中一组子类通过它们的超类共享一些方法的实现,但又各自实现了一些方法。我很清楚如何使用Go的接口,甚至使用了嵌入,但我不太确定是否有任何惯用法可以捕捉到这种预期的行为。
这里有一个具体而又非常熟悉的例子。我将使用Ruby语言。有两种动物,狗和奶牛。所有动物都有一个名字,并且它们可以说话。无论动物的类型如何,设置和获取名称的方式都是相同的;它们发出的声音取决于子类。现在有一个speak
方法,对于所有动物来说都是相同的,但它委托给子类的sound
方法。以下是Ruby代码示例:
class Animal
def initialize(name); @name = name; end
def speak; puts "#{@name} says #{sound()}"; end
end
class Dog < Animal; def sound(); "woof"; end; end
class Cow < Animal; def sound(); "mooo"; end; end
在Go语言中,如何最好地实现这个功能呢?
到目前为止,我尝试了以下代码:
type Animal struct {
name string
}
type Cow struct {
Animal
}
type Dog struct {
Animal
}
我已经能够创建如下的“动物”:
func (d Dog) sound() string {return "woof"}
func (c Cow) sound() string {return "mooo"}
func main() {
d := Dog{Animal{"Sparky"}}
c := Cow{Animal{"Bessie"}}
fmt.Println(d.name)
fmt.Println(c.sound())
}
但我觉得我做错了。我知道我可以将sound()
放在一个接口中,但那样的话具体的动物就成了“发声者”,而不是真正的动物。如果Animal
成为接口,我就无法共享名称和说话的代码了。我意识到Go的设计者只选择了接口,并且选择不直接支持这种在Ruby、Python、Java等语言中常见的经典面向对象用例,但我怀疑应该有某种惯用法或最佳实践来模拟这种情况。有什么首选的方法吗?
英文:
I am looking to translate a "classic OO" example into Go, one in which a set of subclasses implement some methods on their own but they share an implementation of some methods via their superclass. I'm well aware of how to use Go's interfaces and I've even used embedding, but I'm not quite sure what, if any, idioms to employ to capture this intended behavior.
Here's a concrete, and probably a very familiar example. I'll use Ruby. There are two kinds of animals, dogs and cows. All animals have a name and they can speak. The way you set and get the same is the same regardless of the animal type; the sound they make differs depending on the subclass. Now there is a speak
method which is the same for all animals, but it delegates to the subclass's sound
method. Here it is in Ruby:
class Animal
def initialize(name); @name = name; end
def speak; puts "#{@name} says #{sound()}"; end
end
class Dog < Animal; def sound(); "woof"; end; end
class Cow < Animal; def sound(); "mooo"; end; end
How is this best captured in Go?
So far I've tried
type Animal struct {
name string
}
type Cow struct {
Animal
}
type Dog struct {
Animal
}
and I've been able to construct "animals" like so:
func (d Dog) sound() string {return "woof"}
func (c Cow) sound() string {return "mooo"}
func main() {
d := Dog{Animal{"Sparky"}}
c := Cow{Animal{"Bessie"}}
fmt.Println(d.name)
fmt.Println(c.sound())
}
But I feel I'm going about this all wrong. I know I can put sound()
in an interface, but then the specific animals are sounders, not really animals. If Animal
becomes the interface, I can't share the name and the speak code. I realize the designers of Go went with interfaces-only and chose not do directly support this classic OO use case the way we would see it done in Ruby, Python, Java, and so on, but I suspect there should be some idiom or best practice for simulating this. What is the preferred way of doing so?
答案1
得分: 4
> 但我怀疑应该有一些习语或最佳实践来模拟这个。
没有这样的。
如果确实出现这样的情况(在真实的代码中并不经常出现,而主要出现在Java/Ruby/其他代码的翻译中):interface Named { Name() string }
和 interface Sounder { Sound() }
结合成 interface Animal {Named, Sounder}
,然后传递这些动物。
再次强调:"首选的方式"是在不使用继承的情况下重新设计解决方案。
英文:
> but I suspect there should be some idiom or best practice for simulating this.
No there isn't.
If something like that does come up (and it doesn't very often in real code, but mostly in translations of Java/Ruby/whatever code): interface Named { Name() string }
and interface Sounder { Sound() }
combined to interface Animal {Named, Sounder}
and pass those animals around.
Again: The "prefered way" is to remodel the solution without inheritance.
答案2
得分: 3
我认为混淆可能来自于使用复合字面量
构建实例。
这些在单行中创建复杂类型非常完美,并且正如前面的链接所建议的那样,可以减少样板代码。
然而,有时候通过更明确地做事情可以使代码更简单和更易读。我发现在利用嵌入时有时候是这种情况。
引用前面的链接:
>>嵌入类型的方法是免费的
你不是委托给子类的sound
方法,而是透明地使用Animal
的sound
字段来设置和获取“子类”的sound
。
所以我更喜欢的做法是这样的:
package main
import "fmt"
type Animal struct {
name string
sound string
}
type Cow struct {
Animal
}
type Dog struct {
Animal
}
func (a *Animal) Speak() string {
return fmt.Sprintf("%s", a.sound)
}
func main() {
c := new(Cow)
d := new(Dog)
c.name, c.sound = "Bessie", "mooo"
d.name, d.sound = "Sparky", "woof"
fmt.Println(c.Speak())
fmt.Println(d.Speak())
}
输出:
>>mooo
woof
编辑:关于这个主题,有Rob Pike的一句话:
>>Go对面向对象编程采取了一种不同寻常的方法,允许在任何类型上定义方法,而不仅仅是类,但没有任何形式的基于类型的继承,比如子类化。这意味着没有类型层次结构。这是一个有意的设计选择。尽管类型层次结构已经被用来构建很多成功的软件,但我们认为这种模型已经被过度使用,值得退一步思考。
英文:
I think the confusion may be coming from constructing the instances using composite literals
.
These are perfect for creating complex types in single lines, and manage, as the previous link suggests, to cut down on boiler-plate code.
Sometimes however, the code may be simpler and more readable by doing things more explicitly. I find this is sometimes the case when taking advantage of Embedding.
To quote the previous link:
>>The methods of embedded types come along for free
You aren't delegating to the sub-class's sound
method, but the setting and getting of the "sub-class" sound
transparently uses the sound
field of Animal
So my preferred way of doing this would be something like:
package main
import "fmt"
type Animal struct {
name string
sound string
}
type Cow struct {
Animal
}
type Dog struct {
Animal
}
func (a *Animal) Speak() string {
return fmt.Sprintf("%s", a.sound)
}
func main() {
c := new(Cow)
d := new(Dog)
c.name, c.sound = "Bessie", "mooo"
d.name, d.sound = "Sparky", "woof"
fmt.Println(c.Speak())
fmt.Println(d.Speak())
}
Produces:
>>mooo
woof
EDIT: There's a quote from Rob Pike regarding this subject:
>>Go takes an unusual approach to object-oriented programming, allowing methods on any type, not just classes, but without any form of type-based inheritance like subclassing. This means there is no type hierarchy. This was an intentional design choice. Although type hierarchies have been used to build much successful software, it is our opinion that the model has been overused and that it is worth taking a step back.
答案3
得分: 2
你不能将非接口方法附加到接口上。如果动物要说话,它们需要有名字和声音。此外,你可以嵌入私有类型,而你嵌入的是实现细节。根据这些见解,我认为这就是你想要的。
package farm
type Animal interface {
Name() string
Sound() string
}
func Speak(a Animal) string {
return a.Name() + " says " + a.Sound()
}
type animal struct {
name string
}
func (a *animal) Name() string {
return a.name
}
type Cow struct {
animal
}
func NewCow(name string) *Cow {
return &Cow{animal{name}}
}
func (c *Cow) Sound() string {
return "mooo"
}
type Dog struct {
animal
}
func NewDog(name string) *Dog {
return &Dog{animal{name}}
}
func (c *Dog) Sound() string {
return "woof"
}
带有以下 main 函数的代码:
package main
import "fmt"
import "farm"
func main() {
c := farm.NewCow("Betsy")
d := farm.NewDog("Sparky")
// "In classic OOO you'd write c.Speak()"
fmt.Println(farm.Speak(c))
fmt.Println(farm.Speak(d))
}
主函数的 Play 链接:http://play.golang.org/p/YXX6opX8Cy
英文:
You can't attach non-interface methods to an interface. If an animal is to speak they need both a name and a sound. Also you can embed private types and what you've embedded is an implementation detail. Given these insights I think this is what you're after.
package farm
type Animal interface {
Name() string
Sound() string
}
func Speak(a Animal) string {
return a.Name() + " says " + a.Sound()
}
type animal struct {
name string
}
func (a *animal) Name() string {
return a.name
}
type Cow struct {
animal
}
func NewCow(name string) *Cow {
return &Cow{animal{name}}
}
func (c *Cow) Sound() string {
return "mooo"
}
type Dog struct {
animal
}
func NewDog(name string) *Dog {
return &Dog{animal{name}}
}
func (c *Dog) Sound() string {
return "woof"
}
with a main like this:
<!-- language: go -->
package main
import "fmt"
import "farm"
func main() {
c := farm.NewCow("Betsy")
d := farm.NewDog("Sparky")
//"In classic OOO you'd write c.Speak()"
fmt.Println(farm.Speak(c))
fmt.Println(farm.Speak(d))
}
Play link w/ main: http://play.golang.org/p/YXX6opX8Cy
答案4
得分: 0
这是一个Go语言的代码示例,它定义了一个Animal结构体和一个StringSounder接口。Animal结构体包含一个Name字段和一个Sounder接口类型的Sounder字段。Animal结构体还定义了一个Speak方法,用于打印动物的名称和声音。StringSounder是一个自定义类型,实现了Sounder接口的Sound方法。
在main函数中,创建了两个Animal对象d和c,并分别调用它们的Speak方法打印出它们的名称和声音。
你需要对这段代码进行翻译吗?
英文:
What about this?
package main
import (
"fmt"
)
type Sounder interface {
Sound() string
}
type Animal struct {
Name string
Sounder Sounder
}
func (a *Animal) Speak() {
fmt.Printf("%s says %s.\n", a.Name, a.Sounder.Sound())
}
type StringSounder string
func (f StringSounder) Sound() string {
return string(f)
}
func main() {
d := &Animal{"Sparky", StringSounder("woof")}
c := &Animal{"Bessie", StringSounder("mooo")}
d.Speak()
c.Speak()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论