Go 语言中嵌入私有接口的可见性

6
如果我将一个小写名称(private)的接口嵌入到一个大写名称(public)的接口中,我认为定义包之外的代码无法看到已嵌入的私有接口。这个假设正确吗?
type b interface {
    G() int
}

type A interface {
    F() string
    b
}

定义包之外的代码无法 "看到" 嵌入的 b,对吗?外部代码无法通过 A 的实例调用 G


2
你试过做同样的事情吗? - vedhavyas
1个回答

11

在接口中嵌入未导出的接口

在接口中嵌入接口,实际上就是将嵌入接口的方法集合并到嵌入者中,使其成为嵌入者类型的方法集的一部分。无论嵌入的接口类型是否被导出都没有关系。引用规范:接口类型:

接口 T 可以使用(可能带限定符的)接口类型名 E 代替方法规范。这被称为在 T 中嵌入接口 E;它会将 E 的所有(导出和非导出)方法添加到接口 T 中。

在你的代码中,所有发生的事情就是 A 将成为一个具有 2 个方法的接口:F() stringG() int。不会有像 A.b 这样的“字段”,因为 A 是一个接口类型,而不是结构体。因此,这并不特别“有趣”。

如何尝试它?

在任何包中创建一个名为 subplay 的文件夹,subplay.go:

package subplay

type b interface {
    G() int
}

type A interface {
    F() string
    b
}

创建另一个go文件,导入这个subplay,例如:play.go

package main

import "play/subplay"

func main() {
    var a subplay.A
    a.G()
}

而且它可以编译。但是运行时会出现恐慌,因为a没有初始化(或者说仍然是nil),但如果它被初始化了,a.G()调用就不会引发恐慌。
通过以下添加,就不会有运行时恐慌:
在subplay.go中添加:
type aimpl struct{}

func (aimpl) F() string { return "aimpl.F() called" }
func (aimpl) G() int    { return 1 }

func NewA() A {
    return aimpl{}
}

play.go中:

func main() {
    var a subplay.A
    a = subplay.NewA()
    a.G()
}

上述代码可以编译和运行,不会出现错误。

还请注意,您可以在另一个包中创建实现subplay.A的类型,您不需要引用subplay.b,因为方法集是唯一重要的内容。下面的another类型也实现了subplay.A,您可以将其放置在play.go中:

type another struct{}

func (another) F() string { return "aimpl.F() called" }
func (another) G() int    { return 1 }

func main() {
    var a subplay.A
    a = another{}
    a.G()
}

这段代码可以编译和运行,不会出现运行时错误。

在结构体中嵌入未公开的接口

更有趣的情况是,在结构体(而不是接口)中嵌入未公开的结构体(或接口)类型,这会“真正”创建一个A.b字段。此外,嵌入类型的字段和方法会像嵌入到它们自己的结构体中一样被提升到嵌入者中。引用Spec: Struct types:

如果x.f是合法的selector,则称结构体x中嵌入字段f的字段或方法f提升的

提升的字段就像结构体的普通字段一样,只是不能在结构体的composite literals中用作字段名。

定义包之外的代码无法引用嵌入字段A.b,这是正确的,但定义包之外的代码可以调用提升的A.G()方法,因为此标识符不是小写的,所以该限制不适用于它。

非导出标识符的限制是编译器在您尝试引用它们时执行的。从另一个包中写入a.b.G()是一个编译时错误,因为您正在引用非导出标识符(a.b)。当您写a.G()时,您不是在引用任何非导出标识符,只是在引用导出的a.G标识符,因此是允许的。

1
这真的很有趣。我本以为 G 会因为在 b 的“后面”而被隐藏起来。谢谢。 - Ralph
但如果它与A.b字段一起出现,那么a.b.G()将会被轻松调用。您在这里的意思是什么?不太清楚。 - Shahriar
1
@aerokite,措辞不太准确,我进行了编辑。我的意思是,在我的示例中,anil。如果您将非 nil 值分配给它(实现了 subplay.A),那么 a.G() 调用就不会引发恐慌。请参见编辑后的答案。 - icza
公开促进的值可以在定义包之外被引用这一事实,如果由于两个嵌套私有类型具有相同名称而存在歧义,则可能会出现问题。感谢您详尽的答复。 - Ralph
@Ralph 请注意规范中的说明:"...如果 x.f 是一个合法的选择器..."。如果你的结构体嵌入了两种不同类型,它们都有一个相同名称的字段或方法,那么 x.f不是合法的,而且 x.f 选择器将在编译时出现错误。 - icza
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接