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

Export only subset of methods implemented by embedded struct

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

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()
}

这就是嵌入的工作原理,您对此无能为力。 (其实有的,看文末的黑招)

你想要的也许可以通过接口来实现。使您的结构未导出,(B => bC => c),并创建类似函数的“构造函数”,return 接口类型,仅包含您希望发布的方法:

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{}
}

您可能希望调用不同的接口和函数,这只是为了演示。

另请注意,虽然 returned Helloer 接口不包含 World() 方法,但仍然可以使用 type assertion“到达”它,例如:

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

Go Playground 上试试这个。

恶作剧

如果类型嵌入类型 AA 的(字段和)方法得到 提升 将成为 [=58= 的一部分]

A field or method f of an anonymous field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

重点是促销,选择者必须合法Spec: Selectors 描述了 x.f 是如何解决的:

The following rules apply to selectors:

  1. For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.

[...]

这是什么意思?简单地通过嵌入,B.World 将表示 B.A.World 方法,因为它处于最浅的深度。但是如果我们能够实现使得B.A.World不会是最浅的,那么B类型就不会有这个World()方法,因为B.A.World不会升职。

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

的字段
type B struct {
    A
    World int
}

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
}

Go Playground 上试试这个变体。