Golang接口转换为嵌入结构体

huangapple go评论86阅读模式
英文:

Golang interface cast to embedded struct

问题

我想使用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
    // ...
}

然后Rock类型实现了Collidable接口,这样我就可以将它传递给一个期望Collidable类型的Spatial Hash Map。唯一需要做的就是根据我的需求重写Collide()函数。

然而,Circle类型中的函数无法处理Rock类型,即使它内嵌了一个Circle类型。

func (c1 *Circle) DoesCollide(i Collidable) bool {
    switch c2 := value.(type) {
    case Circle:
        // 这里不会触发,因为它是Rock类型(在该包中是未知的)
        // 需要的是类似于
        // if i嵌入了Circle类型 then c2 := 将i转换为Circle类型
    }
}

这种情况是否可行?是否有更好的方法?

英文:

I want to implement a collision library using the interface Collidable

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

It has predefined shapes like.

type Circle struct{
X,Y,Radius float64
}

The idea is that I can do

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

which then implements the interface Collidable, so I can pass it to a Spatial Hash Map (that expects a collidable). Only thing needed to do would be to override the Collide() function to my needs.

However the functions in type circle can not handle the type rock, even tough it has a circle embedded.

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

Is this possible?
Is there a better way?

答案1

得分: 5

你正在尝试在Go中使用面向对象的设计模式和继承,但这不是Go中的正确做法。此外,在Java或其他面向对象的语言中,接口名称以'able'结尾,而在Go中,约定是以'er'结尾。

针对你关于Rock的问题,我建议所有可能与其他物体碰撞的物体都实现一个CollisonShape()方法,该方法返回一个collison.Shaper(例如Circle),你可以用它来测试碰撞。这里的collison是你的包名。

// 这个接口在collison包中定义。
// 任何可能发生碰撞的对象都必须实现该方法。
type Collider interface {
    CollisonShape() Shaper
}

// 这个函数在collison包中定义,
// 用于测试两个Collider是否相互碰撞。
func Collide(c1, c2 Collider) bool {
    shape1, shape2 := c1.CollisonShape(), c2.CollisonShape()
    ...
}

// 这是如何定义一个可以发生碰撞的对象。
type Rock struct {
    shape *collison.Circle
    ...
}

// 实现Collider接口。
// 返回类型必须与接口中的类型相同。
func (r *Rock) CollisonShape() collison.Shaper {
    return r.shape
}

如你所见,我们使用一个方法来访问rock的碰撞形状。这样可以写成:

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

这回答了你关于如何获取Rock的碰撞形状的问题。

如果你想避免在Collide()函数中调用CollisonShape()方法,你将需要直接传递collison.Shaper。

Collide方法在collison包中定义如下:

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

然后你需要这样写:

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

这种设计可能会稍微更高效一些,但代价是代码可读性较差,这在经验丰富的Go程序员中是不被赞同的。

如果你想让Circle成为rock的嵌入结构体,你需要这样定义它。嵌入shape可以节省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应该有一个位置会发生变化,并且有一个宽度和高度不会发生变化的边界框。你可以使用这些值来进行边界框重叠测试,从而节省一些操作。但这是另外一个故事。

英文:

You are trying to use an object oriented design pattern with inheritance. This is not how to do this in Go. Beside, interface names end in 'able' in Java or equivalent object oriented languages. In Go the convention is to end interface names with 'er'.

To answer your question about the Rock, I would suggest that all thing that can collide into another thing implements the method CollisonShape() returning a collison.Shaper (e.g. Circle) that you will use to test collison. Here collison is the name of your package.

<!-- language-all: Go -->

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

As you see, we use a method to access the collison shape of the rock. This allow us to write

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

This answer your question on how to do get the collison Shape of Rock.

If you want to avoid the call to the CollisonShape() method in the Collide() function, you will have to pass directly the collison.Shaper.

The method Collide would be defined in the collison package as

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

You then would have to write

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

This design would be slightly more efficient, but the price to pay is less readable code which is frown upon by experienced Go programmers.

If you want Circle to be an embedded struct in rock you would then have to define it the following way. Embedding the shape saves allocation time for the Circle and some work for the GC.

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

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

If you want to use an anonymous embedded struct, you then would have to write

type Rock struct {
    Circle
    ....
}

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

As you see, the code gets less and less readable, and less convenient to use. The shape is not abstracted anymore. Using anonymous embedded struct should be limited to the very few cases where it really make sense.

By using the initially suggested CollisonShape() method, you could easily change your Rock structure into this one without breaking any code.

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


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

This now make the shape and embedded struct. The use of a method to get the shape decouples the internal implementation of Rock from the access to the shape. You can change the internal implementation of Rock without needing to change code in other places.

This is one of the reason why Go doesn't support inheritence. It creates a very strong dependency and coupling between the base class and derived classes. Experience has shown that people often regret such coupling as the code evolves. Object composition is preferred and recommended and well supported by Go.

If efficiency is your goal, every Collider should have one position which changes and a bounding box with a width and height that doesn't change. You can save a few operations using these values for the bounding box overlap test. But this is another story.

答案2

得分: 0

不确定这个有多离谱,但是我还是把它发出来,以防对你有所帮助。

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

英文:

Not sure how off the mark this is, but posting it in case it helps you in any way.

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

答案3

得分: 0

如果你正在调用一个不可扩展的旧库(只能检测到Circle而不能检测到Rock),似乎只有一个解决方案,那就是传递一个Circle

请参考这个示例,其中我使用了一个非匿名字段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 &{ }
英文:

If you are calling a legacy library which is not extensible (and cannot detect Rock, only Circle), there seems to be only one solution, pass a Circle.

See this example, where I use a non-anonymous field 'c' for Rock
(That means Rock has to implement the interface, which is a simple delegation to 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(&quot;type:&quot;, reflect.TypeOf(s))
	switch st := s.(type) {
	case *Circle:
		fmt.Println(&quot;Shaper Circle %+v&quot;, st)
	default:
		fmt.Println(&quot;Shaper unknown&quot;)
	}
}

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

Then the output is:

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

答案4

得分: 0

我通过稍微修改基类来解决了一个类似的问题。不确定这是否是你要找的解决方案。

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 上尝试一下。

英文:

I have solved a similar problem by slightly modifying the base class. Not sure whether this is the solution you were looking for.

import &quot;fmt&quot;

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(&quot;Shaper Circle %+v\n&quot;, sc.GetCircle())
	} else {
		fmt.Println(&quot;Shaper unknown&quot;)
	}
}

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

That is, add a trivial function GetCircle() so that anything embedding a Circle has a function to get the (possibly embedded) circle. Then anybody needing a circle can trivially write an interface (ShapeWithCircle here) allowing one to test whether GetCircle() is defined and, if so, call it to get the embedded circle.

Play with it at https://play.golang.org/p/IDkjTPrG3Z5 .

huangapple
  • 本文由 发表于 2015年3月14日 02:03:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/29039021.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定