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

时间:2017-06-08 14:53:39

标签: go methods struct embedding

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

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

1 个答案:

答案 0 :(得分: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{}
}

您可能希望将接口和功能调用为不同,这仅用于演示。

另请注意,虽然返回的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上尝试此操作。

肮脏的把戏

如果类型嵌入A类型,A获取提升的方法将成为嵌入式类型method set的一部分(因此成为A类型的方法)。这在Spec: Struct types:

中有详细说明
  

如果f是合法{{3>},则x中的匿名字段的字段或method x.f称为已提升表示该字段或方法f

重点是促销,选择器必须为合法selector描述了x.f如何解决:

  

以下规则适用于选择器:

     
      
  1. 对于类型xT的值*TT不是指针或接口类型,x.f表示字段或方法T中最浅的深度,其中有f。如果没有具有最浅深度的Spec: Selectors,则选择器表达式是非法的。
  2.         

    [...]

这是什么意思?只需嵌入,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表示字段而不是{{ 1}}因为前者处于最浅的深度。请在one f上尝试此操作。

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

这个"脏伎俩的另一个变种"是利用第一条规则的结尾:"如果不是一个 World()深度最浅" 。这可以实现也嵌入另一种类型,另一种结构也具有f字段或方法,例如:

World

Go Playground上尝试此变体。