在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

Playgound link

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