将多种类型合并为一个类型,而无需进行接口化。

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

Combine multiple types into one without interfacing

问题

在一个Go项目中,我需要为实现了名为MyObject的接口的类型定义两种不同的“形状”。这些形状本身是在外部库中定义的类型,它们并没有实现任何共享接口。

MyObject的定义如下:

type MyObject interface {
    GetShape() *Shape // 一些统一的返回值
}

形状的定义如下:

type Circle struct {
    Radius int
    X      int
    Y      int
}

type Square struct {
    X int
    Y int
    W int
    H int
}

func NewCircle(x int, y int, radius int) Circle
func NewSquare(x int, y int, w int, h int) Square

我有一个球和一个盒子,它们实现了MyObject接口:

type Ball struct {
    shape *Circle
}

type Box struct {
    shape *Square
}

func (b *Ball) GetShape() *Shape {
    return b.shape
}

func (s *Square) GetShape() *Shape {
    return s.shape
}

这看起来很简单,使用接口就可以解决。但在这种情况下,我们不能使用接口,因为Circle和Square没有实现任何相同的方法,并且它们位于我们正在工作的包之外。

对于使用圆和正方形的方法,我需要使用类似于以下的方法:

testCircleSquare(circle *Circle, square *Square) bool {}
testSquareSquare(square1 *Square, square2 *Square) bool {}

如何区分或使这两个对象更通用?我目前唯一想到的主意是将它们封装到一个类型中,例如:

type Shape struct {
    circle *Circle
    square *Square
}

并检查circle或square值是否为nil来确定使用哪个对象,但这似乎有些笨拙,并且如果我添加更多的形状,维护起来会很困难。

英文:

In a Go project, I've got to define two different kinds of "shapes" to types that implement an interface called MyObject. The shapes themselves are types defined in an external library, and do not implement any shared interface.

MyObject looks like

type MyObject interface {
    GetShape() *Shape //some unified return value
}

Shapes look like

type Circle struct {
    Radius int
    X int
    Y int
}

type Square struct {
   X int
   Y int
   W int
   H int
}

func NewCircle(x int, y int, radius int) Circle
func NewSquare(x int, y int, w int, h int) Square

I've got a ball and a box that implement MyObject:

type Ball struct {
    shape *Circle
}

type Box struct {
    shape *Square
}

func (b *Ball) GetShape() *Shape {
    return b.shape
}

func (s *Square) GetShape() *Shape {
    return s.shape
}

This seems straightforward enough with interfacing - but we can't use one in this situation since there are no methods implemented by Circle and Square that are identical, plus they are outside of the package we're working in.

For methods using the circle and the square, I need to use methods like

testCircleSquare(circle *Circle, square *Square) bool {}
testSquareSquare(square1 *Square, square2 *Square) bool {}

How can I distinguish or make these two objects more generic? The only idea I had so far was to containerize them into a type like

type Shape struct {
    circle *Circle
    square *Square
}

and check for nil circle or square values to determine which to use, but this seems hacky and difficult to maintain if I add more shapes.

答案1

得分: 2

@Adrian已经解释了在这里使用interface{}的问题

相反,使用适配器模式。创建自己的Shape接口并为预定义的形状创建适配器。

Shape接口(可能应该称为Shape2D,因为3D形状的行为不同)可能如下所示。这样可以获得类型系统的优势,并具有统一的形状接口。

type Shape interface {
    Area() float32
    Perimeter() float32
    X() int
    Y() int
}

然后在现有对象周围创建适配器。不需要包装器,可以为类型定义一个别名(external表示Circle和Square来自其他包)。

type ShapeCircle external.Circle

func (self ShapeCircle) Area() float32 {
    return math.Pi * float32(self.Radius) * float32(self.Radius)
}

...以此类推...

type ShapeSquare external.Square

func (self ShapeSquare) Area() float32 {
    return float32(self.W) * float32(self.H)
}

...以此类推...

现在,您可以将Circle和Square对象复制到它们的Shape适配器中,并将它们用作Shape。

c := external.Circle{ Radius: 10, X: 0, Y: 0 }

shape := ShapeCircle(c)

fmt.Println(shape.Area())

您也可以反过来。

external.Function(external.Circle(shape))

同样,这会创建一个副本。


或者,如果您不喜欢复制,可以在ShapeCircle内部嵌入Circle,在ShapeSquare内部嵌入Square。

type ShapeCircle struct {
    external.Circle
}
type ShapeSquare struct {
    external.Square
}

然后,您可以像以前一样使用ShapeCircle,但是必须给它一个Circle。可能需要创建一个New函数来处理这个问题。

c := ShapeCircle{
    Circle: external.Circle{ Radius: 10, X: 0, Y: 0 }
}

它可以用作Shape。

fmt.Println(c.Area())

c.Circle也可以用作Circle。无需复制。

external.Function(c.Circle)
英文:

@Adrian already explained what's wrong with using interface{} here.

Instead, use the Adapter Pattern. Create your own Shape interface and make adapters for the pre-made shapes.

The Shape interface (it should probably be called Shape2D because 3D shapes behave differently) might look like this. This gives you the advantages of the type system, and having a unified shape interface.

type Shape interface {
    Area() float32
    Perimeter() float32
    X() int
    Y() int
}

Then create adapters around the existing objects. No wrapper is necessary, you can define an alias for the type. (external here represents that Circle and Square come from some other package).

type ShapeCircle external.Circle

func (self ShapeCircle) Area() float32 {
    return math.Pi * float32(self.Radius) * float32(self.Radius)
}

...and so on...

type ShapeSquare external.Square

func (self ShapeSquare) Area() float32 {
    return float32(self.W) * float32(self.H)
}

...and so on...

Now you can copy Circle and Square objects to their Shape adapters and use them as Shape.

c := external.Circle{ Radius: 10, X: 0, Y: 0 }

shape := ShapeCircle(c)

fmt.Println(shape.Area())

You can also go the other way.

external.Function( external.Circle(shape) )

Again, this creates a copy.


Alternatively, if you don't like the copying, you can embed Circle inside ShapeCircle and Square inside ShapeSquare.

type ShapeCircle struct {
    external.Circle
}
type ShapeSquare struct {
    external.Square
}

Then you can use ShapeCircle as before, but you have to give it a Circle. Might want to make New function to take care of that.

c := ShapeCircle{
    Circle: external.Circle{ Radius: 10, X: 0, Y: 0 }
}

It can be used as a Shape.

fmt.Println(c.Area())

And c.Circle can be used as a Circle. No copying necessary.

external.Function( c.Circle )

答案2

得分: 1

如果你无法为它们构建一个特定的接口,你唯一的选择就是使用空接口interface{},它可以保存任何值。然后你需要使用type assertionsreflection来对这些值进行有用的操作。从设计的角度来看,这是一个不寻常的情况,因为你持有一个任意值,无法对其做出任何假设。

英文:

If you can't build a specific interface for them, your only real option is the empty interface interface{}, which can hold any value. You'll then have to use type assertions or reflection to do anything useful with the values. This is an unusual case from a design perspective as you're holding an arbitrary value that you cannot make any assumptions about.

huangapple
  • 本文由 发表于 2017年7月10日 23:57:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/45016646.html
匿名

发表评论

匿名网友

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

确定