实现 Mixin 和编译器行为的不一致

Mixins 可以使用嵌入在 Go (1.4.1) 中实现,并且由于不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 编译器搜索该方法,从 type it self 开始,然后是最后一个嵌入类型。我们可以通过添加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:为什么会这样?


aluckdog
浏览 196回答 2
2回答

GCT1015

问题 1编译器是正确的。它应该如何决定,其中OX.F并Y.F应该使用它?它不能。因此,直接调用所需的方法取决于您:要么使用z.Y.F()或者z.OX.F()编辑:至于为什么你的榜样的工作,直到你定义F上Y,这是中提到的规格:对于 x 类型T或*TwhereT不是指针或接口类型的值,x.f表示最浅深度的字段或方法,T其中存在此类f。如果没有f最浅的深度,则选择器表达式是非法的。(加了重点。)在定义方法之前,最浅的实现是OX.F. 在你定义之后Y.F,F在同一层上变成了两个s,这是非法的。问题2同样,编译器是正确的。您已将类型Y和嵌入OX到Z,而不是*Y和*OX。正如规范中所写,对应指针类型*T的方法集是所有用receiver *Tor声明T的方法的集合(也就是包含了的方法集T)。*T具有 的所有方法T,但不是相反。方法套OX和Y是空的,所以很明显,fmt.Println只是打印他们,如果他们是任何其它种类的结构没有String()定义的方法。

SMILET

Ainar-G 写出简洁的答案规格:对于 T 或 *T 类型的值 x,其中 T 不是指针或接口类型,xf 表示 T 中最浅深度处存在此类 f 的字段或方法。如果没有一个 f 的深度最浅,则选择器表达式是非法的。我想补充一点规格:如果 S 包含匿名字段 T,则 S 和 *S 的方法集都包含接收者 T 的提升方法。 *S 的方法集还包括接收者 *T 的提升方法。因此,如果您仅使用引用来推广诸如fmt.Println(&z)但这会导致选择的歧义,因为 String 方法的可能性很小,因此由于规范,选择器 String 是非法的。编译器必须抱怨,但它没有。这种行为看起来没有具体说明,只能将其解释为我认为的常见打印操作的特例。这将按预期工作var y Y fmt.Println(&y)这是工作示例 游乐场
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go