仅导出嵌入式结构实现的方法子集

Dom*_* M. 3 methods struct embedding go

是否可以只导出嵌入式结构实现的方法的子集?这是一种非常不同的方法来减少代码的复制和粘贴,还有一种更惯用的方法吗?

type A struct {
}

func (a *A) Hello() {
    fmt.Println("Hello!")
}

func (a *A) World() {
    fmt.Println("World!")
}

type B struct {
    A
}

type C struct {
    A
}

func main() {
    b := B{}
    c := C{}

    // B should only export the Hello - function
    b.Hello()

    // C should export both Hello - and World - function
    c.Hello()
    c.World()
}
Run Code Online (Sandbox Code Playgroud)

icz*_*cza 5

这就是嵌入工作的方式,你无能为力.(实际上有,最后看到肮脏的伎俩.)

您可以通过接口实现您想要的功能.使你的结构取消导出,(B=> bC=> c),并创建像构造函数一样的函数,它返回接口类型,只包含你想要发布的方法:

type b struct {
    A
}

type c struct {
    A
}

type Helloer interface {
    Hello()
}

type HelloWorlder interface {
    Helloer
    World()
}

func NewB() Helloer {
    return &b{}
}

func NewC() HelloWorlder {
    return &c{}
}
Run Code Online (Sandbox Code Playgroud)

您可能希望将接口和函数称为不同,这仅用于演示.

另请注意,虽然返回的Helloer接口不包含该World()方法,但仍然可以使用类型断言 "到达"它,例如:

h := NewB() // h is of type Helloer
if hw, ok := h.(HelloWorlder); ok {
    hw.World() // This will succeed with the above implementations
}
Run Code Online (Sandbox Code Playgroud)

Go Playground尝试这个.

肮脏的把戏

如果类型嵌入了类型A,则A获得提升的(fields和)方法将成为嵌入器类型的方法集的一部分(因此成为类型的方法A).这在Spec:Struct类型中有详细说明:

如果是表示该字段或方法的合法选择器,则结构中的匿名字段的字段或方法 称为提升.fxx.ff

重点是促销,选择者必须合法.规范:选择器描述如何x.f解决:

以下规则适用于选择器:

  1. 对于值x类型的T*T其中T不是指针或接口类型,x.f表示在最浅深度域或方法T,其中有这样一个f.如果没有确切的one f深度,则选择器表达式是非法的.

[...]

这是什么意思?简单地通过嵌入,B.World将表示B.A.World方法,即在最浅的深度.但是如果我们能够实现那样B.A.World不会是最浅的,那么类型B将不会有这种World()方法,因为B.A.World不会得到提升.

我们怎样才能做到这一点?我们可以添加一个名称为的字段World:

type B struct {
    A
    World int
}
Run Code Online (Sandbox Code Playgroud)

这种B类型(或更确切地说*B)不具有World()方法,因为B.World表示该字段而不是B.A.World前者处于最浅深度.在Go Playground尝试这个.

同样,这并不妨碍任何人明确引用B.A.World(),因此可以"到达"并调用该方法,我们所实现的只是类型B*B没有World()方法.

这个"肮脏技巧"的另一个变种是利用第一条规则的结尾:"如果没有一条 f最浅的深度".这可以实现为嵌入另一种类型,另一种也具有World字段或方法的结构,例如:

type hideWorld struct{ World int }

type B struct {
    A
    hideWorld
}
Run Code Online (Sandbox Code Playgroud)

Go Playground上试试这个变种.