在Go中为“超类方法实现”建模的最佳方法是什么?
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?
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.
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.
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.
", 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()
}
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:
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