在编译器行为中实现Mixins和不一致

时间:2015-02-02 15:54:14

标签: go mixins

可以使用嵌入在Go(1.4.1)中实现Mixins,因为struct{}不占用内存(据我所知),它适合我们想要添加某些功能的情况,或者只是添加方法实际上与其状态无关的类型,但我们希望避免ParseThing(...)而是写thing.Parse(...)

所以:

type X struct{}

func (x X) F() {
    fmt.Println("functionality in X.F()")
}

type Y struct{ X }
type Z struct{ Y }

然后,如果我们这样做:

var z Z
z.F()

会给我们:

functionality in X.F()

到目前为止一切顺利。

现在让我们使用方法OX添加另一个类型F()并将其嵌入Z

type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x OX) F() {
    fmt.Println("functionality in OX.F()")
}

有趣!现在我们得到functionality in OX.F(),它向我们展示Go编译器搜索方法,从自己键入,然后是最后一个嵌入类型。我们可以通过将F()添加到Z

来检查这一点
func (x Z) F() {
    fmt.Println("functionality in Z.F()")
}

输出为functionality in Z.F()。现在,如果我们删除Z.F()方法并将F()添加到Y

//func (x Z) F() {
//    fmt.Println("functionality in Z.F()")
//}

func (x Y) F() {
    fmt.Println("functionality in Y.F()")
}

然后我们看到此错误ambiguous selector z.F;通过指针重定向没有区别。

问题1:为什么会这样?

间接Y的额外等级意味着其他东西,但让我对此有所了解。正如我猜测的那样func (t T) String() string{}是一个例外。这段代码:

type X struct{}

func (x X) String() string {
    return "in X.String()"
}

type Y struct{ X }
type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x OX) String() string {
    return "in OX.String()"
}

func (x Y) String() string {
    return "in Y.String()"
}

然后这个:

var z Z
fmt.Println(z)

给我们:

{in Y.String() in OX.String()}

哪个是合乎逻辑的。但是如果我们使用指针接收器:

import (
    "fmt"
    "testing"
)

func TestIt(t *testing.T) {
    var z Z
    fmt.Println(z)
}

type X struct{}

func (x *X) String() string {
    return "in X.String()"
}

type Y struct{ X }
type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x *OX) String() string {
    return "in OX.String()"
}

func (x *Y) String() string {
    return "in Y.String()"
}

将打印出来:

{{{}} {}}

问题2 :为什么会这样?

2 个答案:

答案 0 :(得分:8)

问题1

编译器是正确的。它应该如何决定,OX.FY.F应该使用哪个?它不能。因此,您可以直接调用所需的方法:使用

z.Y.F()

z.OX.F()

修改:至于您在[{1}}上定义F之前您的示例有效的原因,the Spec中提到了这一点:

  

对于类型为YT的值x,其中*T不是指针或接口类型,T表示x.f中最浅深度的字段或方法{1}}这里有T如果没有一个f具有最浅的深度,则选择器表达式是非法的。

(强调补充。)

在定义方法之前,最浅的实现是f。在您定义OX.F之后,同一级别上有两个Y.F,这是非法的。

问题2

同样,编译器是正确的。您已将FY类型嵌入OX,而不是Z*Y。正如the Spec所述,

  

相应指针类型*OX的方法集是使用receiver *T*T声明的所有方法的集合(也就是说,它还包含{{1}的方法集}})。

T包含T的所有方法,但不是相反。 *TT的方法集为空,所以显然,OX只是将它们打印出来,好像它们是任何其他类型的结构,没有定义Y方法。

答案 1 :(得分:2)

Ainar-G写得很整洁

Spec:

  

对于类型为T或* T的值,其中T不是指针或接口   type,x.f表示T中最浅深度处的字段或方法   哪里有这样的f。如果没有一个f   最浅的深度,选择器表达式是非法的。

我想添加一点

Spec:

  

如果S包含匿名字段T,则S和* S的方法集都包括带有接收方T的提升方法。* S的方法集还包括带接收方* T的提升方法。

如果你只是使用引用来推广像

这样的方法,那么事情会有效
fmt.Println(&z)

但是这会导致选择的模糊性因为String方法的可能性很小,因此由于规范,选择器String是非法的。编译器必须抱怨,但事实并非如此。此行为看起来未指定,只能作为常见打印操作的特殊情况解释。 这将按预期工作

var y Y
fmt.Println(&y)

这是一个有效的例子 Playground