英文:
Export only subset of methods implemented by embedded struct
问题
可以通过嵌入结构体的方式来实现只导出部分方法。在Go语言中,嵌入结构体会将嵌入结构体的方法也一同继承过来。如果你只想导出部分方法,可以使用接口来实现。
以下是修改后的代码:
package main
import "fmt"
type A struct {
}
func (a *A) Hello() {
fmt.Println("Hello!")
}
func (a *A) World() {
fmt.Println("World!")
}
type Helloer interface {
Hello()
}
type B struct {
A
}
func (b *B) Hello() {
b.A.Hello()
}
type C struct {
A
}
func main() {
b := B{}
c := C{}
// B只导出Hello方法
var bHello Helloer = &b
bHello.Hello()
// C导出Hello和World方法
c.Hello()
c.World()
}
在修改后的代码中,我们定义了一个名为Helloer
的接口,该接口只包含了Hello
方法。然后,我们在结构体B
中实现了Hello
方法,并在该方法中调用了嵌入结构体A
的Hello
方法。这样,通过将B
类型转换为Helloer
接口类型,我们就只能访问到Hello
方法了。
希望对你有帮助!如果还有其他问题,请随时提问。
英文:
Is it possible to export only a subset of methods implemented by an embedded struct?
Is this a very go-unlike way to reduce copy - and - pasting of code and there is a more idiomatic way to do this?
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
得分: 6
这是嵌入的工作原理,你无法改变它。(实际上是可以的,见最后的“dirty trick”部分。)
通过接口可以实现你想要的效果。将结构体设为非导出(B
变为 b
,C
变为 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()
方法,但仍然可以使用类型断言“访问”它,例如:
h := NewB() // h 的类型是 Helloer
if hw, ok := h.(HelloWorlder); ok {
hw.World() // 使用上述实现将成功调用此方法
}
在Go Playground上尝试一下。
“Dirty trick”
如果一个类型嵌入了类型 A
,那么被“提升”的 A
的字段和方法将成为嵌入类型的方法集的一部分(从而成为类型 A
的方法)。这在规范:结构体类型中有详细说明。
如果结构体
x
中的匿名字段f
(字段和方法)是合法的选择器,那么字段或方法f
就被称为“提升”。
重点在于“提升”,选择器必须是合法的。规范:选择器描述了 x.f
的解析过程:
以下规则适用于选择器:
- 对于类型为
T
或*T
的值x
,其中T
不是指针类型或接口类型,x.f
表示T
中最浅层的具有f
的字段或方法。如果最浅层不止一个具有f
的字段或方法,则选择器表达式是非法的。[...]
这意味着什么?通过嵌入,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()
方法的效果。
这个“dirty trick”的另一个变体是利用第一条规则的结尾:“如果最浅层不止一个 f
”。这可以通过嵌入另一个类型来实现,另一个也有 World
字段或方法的结构体,例如:
type hideWorld struct{ World int }
type B struct {
A
hideWorld
}
在Go Playground上尝试这个变体。
英文:
This is how embedding works, there's nothing you can do about it. (Actually there is, see dirty trick at the end.)
What you want may be achieved with interfaces though. Make your structs unexported, (B
=> b
and C
=> c
), and create "constructor" like functions, which return interface types, containing only the methods you wish to publish:
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{}
}
You might want to call the interfaces and functions different, this is just for demonstration.
Also note that while the returned Helloer
interface does not include the World()
method, it is still possible to "reach" it using type assertion, e.g.:
h := NewB() // h is of type Helloer
if hw, ok := h.(HelloWorlder); ok {
hw.World() // This will succeed with the above implementations
}
Try this on the Go Playground.
Dirty trick
If a type embeds the type A
, (fields and) methods of A
that get promoted will become part of the method set of the embedder type (and thus become methods of type A
). This is detailed in Spec: Struct types:
> 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
.
The focus is on the promotion, for which the selector must be legal. Spec: Selectors describes how x.f
is resolved:
> 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.
>
> [...]
What does this mean? Simply by embedding, B.World
will denote the B.A.World
method as that is at the shallowest depth. But if we can achieve so that B.A.World
won't be the shallowest, the type B
won't have this World()
method, because B.A.World
won't get promoted.
How can we achieve that? We may add a field with name World
:
type B struct {
A
World int
}
This B
type (or rather *B
) will not have a World()
method, as B.World
denotes the field and not B.A.World
as the former is at the shallowest depth. Try this on the Go Playground.
Again, this does not prevent anyone to explicitly refer to B.A.World()
, so that method can be "reached" and called, all we achieved is that the type B
or *B
does not have a World()
method.
Another variant of this "dirty trick" is to exploit the end of the first rule: "If there is not exactly one f
with shallowest depth". This can be achieved to also embed another type, another struct which also has a World
field or method, e.g.:
type hideWorld struct{ World int }
type B struct {
A
hideWorld
}
Try this variant on the Go Playground.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论