3

我希望将“经典 OO”示例翻译成 Go,其中一组子类自己实现一些方法,但它们通过它们的超类共享一些方法的实现。我很清楚如何使用 Go 的接口,我什至使用过嵌入,但我不太确定使用什么习语(如果有的话)来捕捉这种预期的行为。

这是一个具体的,可能是一个非常熟悉的例子。我会用红宝石。有两种动物,狗和牛。所有的动物都有名字,它们都会说话。无论动物类型如何,您设置和获取相同的方式都是相同的;它们发出的声音因子类而异。现在有一个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 的设计者只使用接口并选择不直接支持这个经典的 OO 用例,就像我们在 Ruby、Python、Java 等中看到的那样,但我怀疑应该有一些习惯用法或最好的练习模拟这个。这样做的首选方式是什么?

4

4 回答 4

4

但我怀疑应该有一些成语或最佳实践来模拟这一点。

不,没有。

如果确实出现了类似的情况(并且在实际代码中并不常见,但主要是在 Java/Ruby/任何代码的翻译中):interface Named { Name() string }interface Sounder { Sound() }结合interface Animal {Named, Sounder}并传递这些动物。

再次:“首选方式”是在不继承的情况下重塑解决方案。

于 2013-09-30T07:32:39.360 回答
3

我认为混乱可能来自使用composite literals.

这些非常适合在单行中创建复杂类型,并且如上一个链接所建议的那样管理以减少样板代码。

然而,有时,通过更明确地做事,代码可能更简单、更易读。我发现在利用Embedding时有时会出现这种情况。

引用上一个链接:

嵌入式类型的方法免费出现

没有委托给子类的sound方法,但是“子类”的设置和获取sound透明地使用soundAnimal

所以我喜欢这样做的方式是:

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())
}

产生:

呜呜呜
_

游乐场链接

编辑:Rob Pike 引用了关于这个主题的一句话:

Go 对面向对象编程采取了一种不同寻常的方法,允许任何类型的方法,不仅仅是类,但没有任何形式的基于类型的继承,如子类化。这意味着没有类型层次结构。这是一个有意的设计选择。尽管类型层次结构已被用于构建许多成功的软件,但我们认为该模型已被过度使用,值得退后一步。

于 2013-09-30T09:24:15.913 回答
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"
}

主要是这样的:

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))
}

播放链接带主:http ://play.golang.org/p/YXX6opX8Cy

于 2013-09-30T18:16:11.103 回答
0

那这个呢?

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()
}
于 2013-09-30T11:08:22.487 回答