【问题标题】:Golang interface cast to embedded structGolang 接口转换为嵌入式结构
【发布时间】:2015-05-16 08:03:35
【问题描述】:

我想使用 Collidable 接口实现一个碰撞库

type Collidable interface{
    BoundingBox() (float64,float64,float64,float64)
    FastCollisionCheck(c2 Collidable) bool
    DoesCollide(c2 Collidable) bool
    Collide(c2 Collidable)
}

它具有预定义的形状,例如。

type Circle struct{
X,Y,Radius float64
}

我的想法是我可以做到

type Rock struct{
    collision.Circle
    ....
}

然后实现接口 Collidable,所以我可以将它传递给 Spatial Hash Map(它需要一个可碰撞的)。唯一需要做的就是根据我的需要重写 Collide() 函数。

然而type circle中的函数不能处理type rock,即使它嵌入了一个圆。

func (c1 *Circle) DoesCollide(i Collidable) bool{
    switch c2 := value.(type) {
    case Circle:
    //doesn't fire, as it is of type Rock (unknown in this package)
    //Needed is something like
    //if i_embeds_Circle then c2 := i_to_Circle 
    }
}

这可能吗? 有没有更好的办法?

【问题讨论】:

  • 所以这个 (play.golang.org/p/J5bvaEtLbM) 可以说明您的问题?
  • 有点。评论岩石的 Shape() 函数(它使用圆圈)。我的问题是不要进入选择的默认部分,但如果我将一块石头传递给 DoesShape()
  • 所以play.golang.org/p/rg_plLZMSMdefault: 部分用于说明Rock 未被检测为Circle Shaper 即使Rock 具有Circle 作为匿名字段.
  • 是的,这就是问题所在。如何让它将岩石识别为一个圆圈。 (它嵌入)。

标签: interface go


【解决方案1】:

不知道这有多离谱,但发布它以防它对您有任何帮助。

http://play.golang.org/p/JYuIRqwHCm

【讨论】:

  • 在您的示例中,圆圈知道岩石,在这种情况下,我也可以简单地将接口转换为正确的类型。问题是,碰撞在它自己的库中,没有进一步了解以后采取某些形状的对象。
【解决方案2】:

如果您调用的是不可扩展的遗留库(并且无法检测到Rock,只能检测到Circle),似乎只有一种解决方案,请传递Circle

请参阅this example,其中我使用非匿名字段“c”作为Rock
(这意味着Rock 必须实现接口,这是对Circle.Shape() 的简单委托)

type Shaper interface {
    Shape()
}

type Circle struct{}

func (c *Circle) Shape() {}

type Rock struct{ c *Circle }

func (r *Rock) Shape() { r.Shape() }

func DoesShape(s Shaper) {
    fmt.Println("type:", reflect.TypeOf(s))
    switch st := s.(type) {
    case *Circle:
        fmt.Println("Shaper Circle %+v", st)
    default:
        fmt.Println("Shaper unknown")
    }
}

func main() {
    c := &Circle{}
    DoesShape(c)
    r := &Rock{c}
    DoesShape(r.c)
}

那么输出是:

type: *main.Circle
Shaper Circle %+v &{}
type: *main.Circle
Shaper Circle %+v &{}

【讨论】:

  • 将你的界面命名为Shaper 而不是IShaper 可能更符合习惯。只是在说'。 :)
  • 在这种情况下,我无法覆盖两个对象碰撞时调用的 Collide 函数,我需要为嵌入形状圆的对象自定义该函数。
  • @user2089648 true:嵌入(通过匿名字段)类型A在类型B中不会使B成为A。这意味着需要一种不同的方法。如果Rock 实现Collidable,至少你的库会调用RockCollide() 版本吗?
  • Type Rock 有它自己的 Collide() 版本,是的,但没有 DoesCollide() 本身的版本。然而,由于 Rock 嵌入了 Circle,它符合 Collidable 的条件,并且调用了 Circle 的 DoesCollide() 版本。但是,DoesCollide 会失败,因为它无法将参数“c2 Collidable”转换为 Circle。
  • @user2089648 好的,所以全嵌入式方法在这里不合适。
【解决方案3】:

您正在尝试使用带有继承的面向对象设计模式。这不是在 Go 中如何做到这一点。此外,接口名称在 Java 或等效的面向对象语言中以“able”结尾。在 Go 中,约定是以“er”结尾的接口名称。

为了回答你关于 Rock 的问题,我建议所有可以碰撞到另一个东西的东西都实现方法 CollisonShape() 返回一个 collison.Shaper(例如 Circle),你将使用它来测试 collison。这里 collison 是你的包的名称。

// This interface is defined in the collison package.
// Any object that may collide must implement that method.
type Collider interface {
    CollisonShape() Shaper
}

// This function defined in the collison package 
// test if two Collider collide into each other.
func Collide(c1, c2 Collider) bool {
    shape1, shape2 := c1.CollisonShape(), c2.CollisonShape()
    ...
}

// This is how you would define an object that can collide.
type Rock struct {
    shape *collison.Circle
    ...
}
// Implements the Collider interface.
// The return type must be the same as in the interface.
func (r *Rock) CollisonShape() collison.Shaper {
    return r.shape
}

如您所见,我们使用一种方法来访问岩石的 collison 形状。这让我们可以写

if collison.Collide(rock, spaceCraft) {...}

这回答了您关于如何获得 collison Shape of Rock 的问题。

如果要避免在 Collide() 函数中调用 CollisonShape() 方法,则必须直接传递 collison.Shaper。

方法 Collide 将在 collison 包中定义为

func Collide(shape1, shape2 Shaper) bool {...}

然后你必须写

if collison.Collide(rock.shape, spacecraft.shape) {...}

这种设计会稍微高效一些,但要付出的代价是可读性较差的代码,这是经验丰富的 Go 程序员所不喜欢的。

如果您希望 Circle 成为 rock 中的嵌入式结构,则必须按以下方式定义它。嵌入形状可以节省 Circle 的分配时间和 GC 的一些工作。

type Rock struct {
    shape collison.Circle
    ....
}

if collison.Collide(&rock.shape, &spacecraft.shape) {...}

如果你想使用匿名的嵌入式结构,那么你必须写

type Rock struct {
    Circle
    ....
}

if collison.Collide(&rock.Circle, &spacecraft.Rectangle) {...}

如您所见,代码的可读性越来越差,使用起来也越来越不方便。形状不再抽象。使用匿名嵌入式结构应仅限于真正有意义的极少数情况。

通过使用最初建议的 CollisonShape() 方法,您可以轻松地将 Rock 结构更改为这个结构,而无需破坏任何代码。

type Rock struct {
    shape collison.Circle
    ...
}


func (r *Rock) CollisonShape() collison.Shaper {
    return &r.shape
}

现在制作形状和嵌入结构。使用获取形状的方法将 Rock 的内部实现与对形状的访问分离。您可以更改 Rock 的内部实现,而无需更改其他地方的代码。

这也是 Go 不支持继承的原因之一。它在基类和派生类之间创建了非常强的依赖和耦合。经验表明,随着代码的发展,人们常常会后悔这种耦合。对象组合是 Go 的首选和推荐并得到很好的支持。

如果您的目标是效率,那么每个 Collider 都应该有一个可以改变的位置和一个宽度和高度不变的边界框。您可以使用这些值为边界框重叠测试保存一些操作。但这是另一个故事。

【讨论】:

  • 我想知道,如果 Circle 本身通过一个方法 CollisionShape 来实现 Collider,那么嵌入匿名 Circle 不会使 Rock 在您上一个示例的扩展中也实现 Collider 吗?这样,只需让 Circle 实现 Collider 并立即调用 c2.CollisionShape().(type) 而不是 c2.(type) 即可解决 OP。我错过了什么吗?我觉得 CollisionShape 方法是使您的想法可行的精髓。
【解决方案4】:

我通过稍微修改基类解决了类似的问题。不确定这是否是您正在寻找的解决方案。

import "fmt"

type IShaper interface {
    Shape()
}

type Rect struct{}

func (r *Rect) Shape() {}

type Circle struct{}

func (c *Circle) GetCircle() *Circle { return c }

func (c *Circle) Shape() {}

type Rock struct{ *Circle }

type ShapeWithCircle interface {
    GetCircle() *Circle
}

func DoesShape(s IShaper) {
    if sc, ok := s.(ShapeWithCircle); ok {
        fmt.Printf("Shaper Circle %+v\n", sc.GetCircle())
    } else {
        fmt.Println("Shaper unknown")
    }
}

func main() {
    DoesShape(&Circle{})
    DoesShape(&Rock{&Circle{}})
    DoesShape(&Rect{})
}

也就是说,添加一个普通函数 GetCircle() 以便任何嵌入 Circle 的东西都有一个函数来获取(可能嵌入的)圆。然后任何需要圆的人都可以简单地编写一个接口(此处为 ShapeWithCircle),允许测试是否定义了 GetCircle(),如果是,则调用它来获取嵌入的圆。

https://play.golang.org/p/IDkjTPrG3Z5 玩它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-02
    • 1970-01-01
    • 2015-01-26
    • 2023-03-04
    • 2016-10-15
    • 2017-12-24
    • 2015-10-20
    • 2014-08-11
    相关资源
    最近更新 更多