确保嵌入式结构实现接口而不会引起歧义 [英] Ensuring embedded structs implement interface without introducing ambiguity
问题描述
我试图通过更好地定义接口并使用嵌入式结构重用功能来清理代码库.就我而言,我有许多可以链接到各种对象的实体类型.我想定义捕获需求的接口和实现接口的结构,然后将这些接口嵌入到实体中.
I'm trying to clean up my code base by doing a better job defining interfaces and using embedded structs to reuse functionality. In my case I have many entity types that can be linked to various objects. I want to define interfaces that capture the requirements and structs that implement the interfaces which can then be embedded into the entities.
// All entities implement this interface
type Entity interface {
Identifier()
Type()
}
// Interface for entities that can link Foos
type FooLinker interface {
LinkFoo()
}
type FooLinkerEntity struct {
Foo []*Foo
}
func (f *FooLinkerEntity) LinkFoo() {
// Issue: Need to access Identifier() and Type() here
// but FooLinkerEntity doesn't implement Entity
}
// Interface for entities that can link Bars
type BarLinker interface {
LinkBar()
}
type BarLinkerEntity struct {
Bar []*Bar
}
func (b *BarLinkerEntity) LinkBar() {
// Issues: Need to access Identifier() and Type() here
// but BarLinkerEntity doesn't implement Entity
}
所以我的第一个想法是让FooLinkerEntity和BarLinkerEntity仅实现Entity接口.
So my first thought was to have FooLinkerEntity and BarLinkerEntity just implement the Entity interface.
// Implementation of Entity interface
type EntityModel struct {
Id string
Object string
}
func (e *EntityModel) Identifier() { return e.Id }
func (e *EntityModel) Type() { return e.Type }
type FooLinkerEntity struct {
EntityModel
Foo []*Foo
}
type BarLinkerEntity struct {
EntityModel
Bar []*Bar
}
但是,对于可以同时链接Foos和Bars的任何类型,这最终都会导致歧义错误.
However, this ends up with an ambiguity error for any types that can link both Foos and Bars.
// Baz.Identifier() is ambiguous between EntityModel, FooLinkerEntity,
// and BarLinkerEntity.
type Baz struct {
EntityModel
FooLinkerEntity
BarLinkerEntity
}
构造这种类型的代码的正确Go方法是什么?我是否只是在LinkFoo()
和LinkBar()
中进行类型断言以获取Identifier()
和Type()
?有什么方法可以在编译时而不是运行时进行此检查吗?
What's the correct Go way to structure this type of code? Do I just do a type assertion in LinkFoo()
and LinkBar()
to get to Identifier()
and Type()
? Is there any way to get this check at compile time instead of runtime?
推荐答案
Go是不是(很)的一种面向对象的语言:它没有类,并且没有类型继承;但它在struct
级别和interface
级别上都支持称为 emdding 的类似构造,并且确实具有
Go is not (quite) an object oriented language: it does not have classes and it does not have type inheritance; but it supports a similar construct called embedding both on struct
level and on interface
level, and it does have methods.
因此,您应该停止在OOP中思考,而开始在组成中思考.由于您在评论中说过FooLinkerEntity
永远不会单独使用,因此可以帮助我们以一种简洁的方式实现您想要的东西.
So you should stop thinking in OOP and start thinking in composition. Since you said in your comments that FooLinkerEntity
will never be used on its own, that helps us achieve what you want in a clean way.
我将使用新名称和较少的功能来专注于问题和解决方案,这将导致代码缩短,并且也更易于理解.
I will use new names and less functionality to concentrate on the problem and solution, which results in shorter code and which is also easier to understand.
可以在游乐场上查看和测试完整代码.
The full code can be viewed and tested on the Go Playground.
简单的Entity
及其实现如下所示:
The simple Entity
and its implementation will look like this:
type Entity interface {
Id() int
}
type EntityImpl struct{ id int }
func (e *EntityImpl) Id() int { return e.id }
Foo and Bar
在您的示例中,FooLinkerEntity
和BarLinkerEntity
只是装饰器,因此它们不需要嵌入(在OOP中为 extend )Entity
,并且它们实现无需嵌入EntityImpl
.但是,由于我们要使用Entity.Id()
方法,因此我们需要一个Entity
值,该值可以为EntityImpl
,也可以不为EntityImpl
,但是我们不限制它们的实现.另外,我们可以选择嵌入它或将其设置为常规"结构字段,这无关紧要(两者都可行):
Foo and Bar
In your example FooLinkerEntity
and BarLinkerEntity
are just decorators, so they don't need to embed (extend in OOP) Entity
, and their implementations don't need to embed EntityImpl
. However, since we want to use the Entity.Id()
method, we need an Entity
value, which may or may not be EntityImpl
, but let's not restrict their implementation. Also we may choose to embed it or make it a "regular" struct field, it doesn't matter (both works):
type Foo interface {
SayFoo()
}
type FooImpl struct {
Entity
}
func (f *FooImpl) SayFoo() { fmt.Println("Foo", f.Id()) }
type Bar interface {
SayBar()
}
type BarImpl struct {
Entity
}
func (b *BarImpl) SayBar() { fmt.Println("Bar", b.Id()) }
使用Foo
和Bar
:
f := FooImpl{&EntityImpl{1}}
f.SayFoo()
b := BarImpl{&EntityImpl{2}}
b.SayBar()
输出:
Foo 1
Bar 2
FooBarEntity
现在让我们看一个真实"实体,它是一个Entity
(实现为Entity
),并且具有Foo
和Bar
提供的功能:
FooBarEntity
Now let's see a "real" entity which is an Entity
(implements Entity
) and has both the features provided by Foo
and Bar
:
type FooBarEntity interface {
Entity
Foo
Bar
SayFooBar()
}
type FooBarEntityImpl struct {
*EntityImpl
FooImpl
BarImpl
}
func (x *FooBarEntityImpl) SayFooBar() {
fmt.Println("FooBar", x.Id(), x.FooImpl.Id(), x.BarImpl.Id())
}
使用FooBarEntity
:
e := &EntityImpl{3}
x := FooBarEntityImpl{e, FooImpl{e}, BarImpl{e}}
x.SayFoo()
x.SayBar()
x.SayFooBar()
输出:
Foo 3
Bar 3
FooBar 3 3 3
FooBarEntity第2轮
如果FooBarEntityImpl
不需要知道(不使用)Entity
,Foo
和Bar
实现的内部(在我们的情况下为EntityImpl
,FooImpl
和BarImpl
) ,我们可以选择仅嵌入接口,而不嵌入实现(但是在这种情况下,我们不能调用x.FooImpl.Id()
,因为Foo
没有实现Entity
-这是实现细节,这是我们最初声明的内容,不需要/使用它):
FooBarEntity round #2
If the FooBarEntityImpl
does not need to know (does not use) the internals of the Entity
, Foo
and Bar
implementations (EntityImpl
, FooImpl
and BarImpl
in our cases), we may choose to embed only the interfaces and not the implementations (but in this case we can't call x.FooImpl.Id()
because Foo
does not implement Entity
- that is an implementation detail which was our initial statement that we don't need / use it):
type FooBarEntityImpl struct {
Entity
Foo
Bar
}
func (x *FooBarEntityImpl) SayFooBar() { fmt.Println("FooBar", x.Id()) }
其用法相同:
e := &EntityImpl{3}
x := FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
x.SayFoo()
x.SayBar()
x.SayFooBar()
其输出:
Foo 3
Bar 3
FooBar 3
在游乐场上尝试使用此变体.
请注意,在创建FooBarEntityImpl
时,将在多个复合文字中使用Entity
值.由于我们只创建了一个Entity
(EntityImpl
),并且在所有地方都使用了它,因此在不同的实现类中仅使用了一个 id,因此每个结构仅传递一个引用",不是重复/副本.这也是预期/要求的用法.
Note that when creating FooBarEntityImpl
, a value of Entity
is to be used in multiple composite literals. Since we created only one Entity
(EntityImpl
) and we used this in all places, there is only one id used in different implementation classes, only a "reference" is passed to each structs, not a duplicate / copy. This is also the intended / required usage.
由于FooBarEntityImpl
的创建很简单且容易出错,因此建议创建类似构造函数的函数:
Since FooBarEntityImpl
creation is non-trivial and error-prone, it is recommended to create a constructor-like function:
func NewFooBarEntity(id int) FooBarEntity {
e := &EntityImpl{id}
return &FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
}
请注意,工厂函数NewFooBarEntity()
返回的是接口类型的值,而不是实现类型的值(应遵循的优良作法).
Note that the factory function NewFooBarEntity()
returns a value of interface type and not the implementation type (good practice to be followed).
优良作法是不导出实现类型,而仅导出接口,因此实现名称为entityImpl
,fooImpl
,barImpl
,fooBarEntityImpl
.
It is also a good practice to make the implementation types un-exported, and only export the interfaces, so implementation names would be entityImpl
, fooImpl
, barImpl
, fooBarEntityImpl
.
一些相关问题值得一查
这篇关于确保嵌入式结构实现接口而不会引起歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!