【问题标题】:What is the most idiomatic way to mimic inheritance in Go in this specific case?在这种特定情况下,模仿 Go 中继承的最惯用的方法是什么?
【发布时间】:2019-04-29 15:43:22
【问题描述】:

我一直在思考这个特殊的问题,我应该如何以最干净的方式解决它。

想象一个如下所示的应用程序:

type AreaCalculator interface {
  Area() int
}

type Rectangle struct {
    color  string
    width  int
    height int
}

type (r *Rectangle) Area() int {
   return r.width * r.height
}

type Circle struct {
    color    string
    diameter int
}

type (c *Circle) Area() int {
   return r.diameter / 2 * r.diameter / 2 * π
}

type Canvas struct {
    children []AreaCalculator
}

func (c *Canvas) String() {
    for child := range c.children {
        fmt.Println("Area of child with color ", child.color, " ", child.Area())
    }
}

这个例子显然不能编译,因为虽然 Canvas 的 String() 方法可以调用 c.Area(),但它不能访问 c.color,因为无法确保实现 AreaCalculator 的结构具有该属性。

我能想到的一个解决方案是这样做:

type AreaCalculator interface {
  Area() int
  Color() string
}

type Rectangle struct {
    color  string
    width  int
    height int
}

type (r *Rectangle) Color() string {
   return r.color
}

type (r *Rectangle) Area() int {
   return r.width * r.height
}

type Circle struct {
    color    string
    diameter int
}

type (c *Circle) Area() int {
   return r.diameter / 2 * r.diameter / 2 * π
}
type (c *Circle) Color() string {
   return c.color
}

type Canvas struct {
    children []AreaCalculator
}

func (c *Canvas) String() {
    for child := range c.children {
        fmt.Println("Area of child with color ", child.Color(), " ", child.Area())
    }
}

另一种方法是尝试这样的事情:

type Shape struct {
    Area func() int 
    color string
    diameter int
    width int
    height int
}

func NewCircle() Shape {
    // Shape initialisation to represent a Circle. Setting Area func here
}


func NewRectangle() Shape {
    // Shape initialisation to represent a Rectangle. Setting Area func here
}

type Canvas struct {
    children []Shape
}

func (c *Canvas) String() {
    for child := range c.children {
        fmt.Println("Area of child with color", child.color, " ", child.Area())
    }
}

这些选项对我来说似乎都不干净。我敢肯定,有一种我想不出的更清洁的解决方案。

【问题讨论】:

  • 啊,你是对的,这没有任何意义。不过这应该没什么区别吧?
  • 惯用的解决方案是:重新设计。
  • 是的,我的问题暗示我已经知道了。如果您已经提出建议,您将如何处理? @Volker

标签: go design-patterns


【解决方案1】:

这里的关键观察是如果你需要一个特定的功能,让它明确。也不要代表他们做其他对象的工作。

还要注意String() 必须返回一个字符串,而不是写入stdout

package main

import (
    "fmt"
    "math"
    "strings"
)

type AreaCalculator interface {
    fmt.Stringer
    Area() int
}

type Rectangle struct {
    color  string
    width  int
    height int
}

func (r *Rectangle) Area() int {
    return r.width * r.height
}

func (r *Rectangle) String() string {
    return fmt.Sprintf("I'm a rectangle %d", r.width)
}

type Circle struct {
    color    string
    diameter int
}

func (c *Circle) Area() int {
    area := math.Round(float64(c.diameter*c.diameter) * math.Pi / float64(4))
    return int(area)
}

func (c *Circle) String() string {
    return fmt.Sprintf("I'm a circle: %d", c.diameter)
}

type Canvas struct {
    children []AreaCalculator
}

func (c *Canvas) String() string {
    lines := make([]string, 0)
    for _, child := range c.children {
        lines = append(lines, child.String())
    }
    return strings.Join(lines, "\n")
}

func main() {
    circle := &Circle{color: "red", diameter: 2}
    rect := &Rectangle{color: "blue", width: 3, height: 4}

    canvas := &Canvas{
        children: []AreaCalculator{circle, rect},
    }

    fmt.Println(canvas.String())
}

【讨论】:

  • 在这种情况下,Canvas 需要知道它是儿童的颜色,这一点很重要。我应该说得更清楚一点,对不起。你不可能知道。如果不是这种情况,您的解决方案将非常适合。感谢您抽出宝贵时间!在这种情况下,你会像 @Rob Napier 那样处理它吗?
【解决方案2】:

一个重要的起点是你不应该在 Go 中模仿继承。 Go 没有继承。它有接口,也有嵌入。他们没有忘记包括继承; it's intentionally not part of the language。 Go 鼓励组合。

您的Canvas 需要的不仅仅是AreaCalculator。它需要提供颜色的东西。你需要表达出来。例如,您可以这样做:

type DrawableShape interface {
  AreaCalculator
  Color() string
}

然后您将为RectangleCircle 实现Color()

func (r Rectangle) Color() string {
  return r.color
}

func (c Circle) Color() string {
  return c.color
}

children 将是[]DrawableShape

children []DrawableShape

这将留下类似 this 的内容(基于 Mohammad Nasirifar 的代码)。

package main

import (
    "fmt"
    "math"
    "strings"
)

type AreaCalculator interface {
    Area() int
}

type DrawableShape interface {
  AreaCalculator
  Color() string
}

type Rectangle struct {
    color  string
    width  int
    height int
}

func (r Rectangle) Area() int {
    return r.width * r.height
}

func (r Rectangle) Color() string {
  return r.color
}

type Circle struct {
    color    string
    diameter int
}

func (c Circle) Area() int {
    area := math.Round(float64(c.diameter*c.diameter) * math.Pi / float64(4))
    return int(area)
}

func (c Circle) Color() string {
  return c.color
}

type Canvas struct {
    children []DrawableShape
}

func (c Canvas) String() string {
    lines := make([]string, 0)
    for _, child := range c.children {
        lines = append(lines, fmt.Sprintf("Area of child with color %s %d", child.Color(), child.Area()))
    }
    return strings.Join(lines, "\n")
}

func main() {
    circle := &Circle{color: "red", diameter: 2}
    rect := &Rectangle{color: "blue", width: 3, height: 4}

    canvas := &Canvas{
        children: []DrawableShape{circle, rect},
    }

    fmt.Println(canvas.String())
}

【讨论】:

  • 哦,这似乎与我所描述的一个选项有点相似,所以我没有想错。我可能已经习惯了这种思维方式。看到你的答案肯定有很大帮助。感谢您抽出宝贵时间,我真的很感激!
猜你喜欢
  • 2014-05-05
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
  • 1970-01-01
  • 1970-01-01
  • 2010-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多