【问题标题】:What is the best way to model a "superclass method implementation" in Go?在 Go 中建模“超类方法实现”的最佳方法是什么?
【发布时间】:2013-10-05 22:13:03
【问题描述】:

我希望将“经典 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 等中看到的那样,但我怀疑应该有一些 模拟这个的成语或最佳实践。这样做的首选方式是什么?

【问题讨论】:

    标签: ruby inheritance interface go


    【解决方案1】:

    您不能将非接口方法附加到接口。如果动物要说话,它们需要名字和声音。您还可以嵌入私有类型,您嵌入的是实现细节。鉴于这些见解,我认为这就是您所追求的。

    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

    【讨论】:

      【解决方案2】:

      这个怎么样?

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

      【讨论】:

        【解决方案3】:

        我认为混淆可能来自使用composite literals 构建实例。

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

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

        引用上一个链接:

        嵌入式类型的方法免费提供

        没有委派给子类的sound 方法,但是“子类”sound 的设置和获取透明地使用了@987654329 的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())
        }
        

        生产:

        哞哞
        汪汪

        Playgound link

        编辑:关于这个主题有a quote from Rob Pike

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

        【讨论】:

        • 很好的答案和 +1 提供相关的 Rob Pike 报价。我知道我们并没有在 Go 中做我所询问的确切类型的事情(实现继承),但似乎仍然应该有一种方法来捕获业务规则,例如“All cows say mooo”。您的解决方案很接近,可能是首选的围棋方法,但如果有任何方法可以将动物的类型与其声音联系起来,那么在解决方案中会很棒。
        【解决方案4】:

        但我怀疑应该有一些习惯用法或最佳实践来模拟这一点。

        没有。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多