作者:谢海武181_160 | 来源:互联网 | 2022-12-07 20:12
让我们说在第三方库中我们有一个接口和一个实现这个接口的结构.我们还假设有一个函数将ParentInterface作为参数,它对不同类型具有不同的行为.
type ParentInterface interface {
SomeMethod()
}
type ParentStruct struct {
...
}
func SomeFunction(p ParentInterface) {
switch x := p.Type {
case ParentStruct:
return 1
}
return 0
}
在我们的代码中,我们想要使用这个接口,但是使用我们的增强行为,所以我们将它嵌入到我们自己的结构中.编译器实际上允许我们ParentInterface
直接在我的struct上调用函数:
type MyStruct struct {
ParentInterface
}
parentStruct := ParentStruct{...}
myStruct := MyStruct{parentStruct}
parentStruct.SomeMethod() // Compiler OK.
myStruct.SomeMethod() // Compiler OK. Result is same. Great.
SomeFunction(parentStruct) // Compiler OK. Result is 1.
SomeFunction(myStruct.ParentInterface) // Compiler OK. Result is 1.
SomeFunction(myStruct) // Compiler OK. Result is 0. (!)
最后一例不是问题吗?我不止一次遇到过这种错误.因为我愉快地使用MyStruct
作为别名ParentInterface
在我的代码(这就是为什么我在首位定义它),它是很难永远记住,我们不能称之为SomeFunction
上MyStruct
直接(编译器说,我们可以!).
那么避免这种错误的最佳做法是什么?或者它实际上是编译器的一个缺陷,它应该禁止使用,SomeFunction(myStruct)
因为结果是不可信的?
1> icza..:
这里没有编译器错误,您的经验结果是预期的结果.
您的SomeFunction()
函数明确声明它希望根据传递的接口值的动态类型执行不同的操作,这正是发生的事情.
我们首先介绍接口,因此我们不必关心实现接口的动态类型.界面为我们提供了对现有方法的保证,这些是你应该依赖的唯一东西,你应该只调用那些方法,而不是做一些类型切换或断言功夫.
当然这是理想的世界,但你应该尽可能地坚持下去.
即使在某些情况下,您无法将所有内容都放入接口中,但如果需要其他功能,则可以再次键入断言另一个接口而不是其中的具体类型.
一个典型的例子就是编写一个http.Handler
将响应编写器作为接口的地方:http.ResponseWriter
.它非常简约,但实际传递的类型可以做得更多.要访问"更多",您可以使用其他类型断言来获取额外的接口,例如http.Pusher
或http.Flusher
.
在Go中,没有继承和多态.去赞成作文.将类型嵌入到另一个类型(struct)中时,嵌入类型的方法集将成为嵌入器类型的一部分.这意味着嵌入式实现的任何接口,嵌入器也将实现这些接口.并且调用那些实现的接口的方法会将调用"转发"到嵌入类型,也就是说,那些方法调用的接收器将是嵌入的值.除非您通过提供自己的实现,并将接收器类型作为嵌入器类型来"覆盖"这些方法.但即使在这种情况下,虚拟路由也不会发生.这意味着如果嵌入类型具有方法A()
和调用的B()
实现,如果您在嵌入器上提供自己的方法,则调用(嵌入类型)将不会调用您的嵌入类型.A()
B()
B()
A()
B()
这不是要避免的东西(你无法避免),这是要了解的东西(要生活的东西).如果你知道它是如何工作的,你只需要考虑到这一点并用它来计算.
因为我很乐意在我的代码中使用它MyStruct
作为别名ParentInterface
(这就是为什么我首先定义它)
您不应该使用嵌入来创建别名,这是滥用嵌入.在您自己中嵌入类型不会是别名.检查具体类型的现有方法的实现将在您遇到时"失败"(意味着它们找不到与其预期的具体类型匹配).
除非您想"覆盖"某些方法或以这种方式实现某些接口,否则不应使用嵌入.只需使用原始类型.最简单,最干净.如果你需要别名,Go 1.9引入了类型别名功能,其语法是:
type NewType = ExistingType
在上述声明NewType
与之相同之后ExistingType
,它们将完全可互换(因此具有相同的方法集).但是要知道这并没有为语言添加任何新的"真实"功能,没有它们就可以使用类型别名来实现.它主要是为了支持更简单,渐进的代码重构.