我正在尝试通过更好地定义接口和使用嵌入式结构来重用功能来清理我的代码库。在我的例子中,我有许多可以链接到各种对象的实体类型。我想定义捕获实现接口的需求和结构的接口,然后可以将这些接口嵌入到实体中。
// 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实现实体接口。
// 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的类型,最终都会出现歧义错误。
// Baz.Identifier() is ambiguous between EntityModel, FooLinkerEntity,
// and BarLinkerEntity.
type Baz struct {
EntityModel
FooLinkerEntity
BarLinkerEntity
}
构建此类代码的Go方法是什么?我只是在LinkFoo()
和LinkBar()
中执行类型断言才能转到Identifier()
和Type()
吗?有没有办法在编译时而不是运行时进行检查?
答案 0 :(得分:2)
Go是not (quite) an object oriented language:它没有类,而does not have type inheritance;但是它在struct
级别和interface
级别上支持一个名为 embedding 的类似构造,它确实有methods。
所以你应该停止在OOP中思考并开始考虑组合。既然您在评论中说FooLinkerEntity
永远不会单独使用,那么这有助于我们以干净的方式实现您想要的目标。
我将使用新名称和更少功能来专注于问题和解决方案,从而缩短代码并且更容易理解。
可以在Go Playground上查看和测试完整代码。
简单的Entity
及其实现将如下所示:
type Entity interface {
Id() int
}
type EntityImpl struct{ id int }
func (e *EntityImpl) Id() int { return e.id }
在您的示例中FooLinkerEntity
和BarLinkerEntity
只是装饰器,因此不需要嵌入(扩展在OOP中){{ 1}},他们的实现不需要嵌入Entity
。但是,由于我们要使用EntityImpl
方法,因此我们需要一个Entity.Id()
值,该值可能是Entity
,也可能不是EntityImpl
,但不要限制它们的实现。我们也可以选择嵌入它或使它成为“常规”结构域,它无所谓(两者都有效):
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
现在让我们看一个“真实”的实体,它是一个Entity
(实现Entity
)并具有Foo
和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
如果FooBarEntityImpl
不需要知道(不使用)Entity
,Foo
和Bar
实施的内部(EntityImpl
,{在我们的案例中{1}}和FooImpl
,我们可能会选择仅嵌入接口而不是实现(但在这种情况下,我们无法调用BarImpl
,因为x.FooImpl.Id()
没有实现Foo
- 这是一个实现细节,这是我们不需要/使用它的初始声明):
Entity
它的用法是一样的:
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()
在Go Playground上尝试此变体。
请注意,在创建Foo 3
Bar 3
FooBar 3
时,会在多个复合文字中使用FooBarEntityImpl
的值。由于我们只创建了一个Entity
(Entity
)并且我们在所有地方都使用了这个,因此在不同的实现类中只使用了一个 id,只传递了一个“引用”每个结构,而不是副本/副本。这也是预期/必需的用法。
由于EntityImpl
创建非常重要且容易出错,因此建议创建类似构造函数的函数:
FooBarEntityImpl
请注意,工厂函数func NewFooBarEntity(id int) FooBarEntity {
e := &EntityImpl{id}
return &FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
}
返回接口类型的值,而不是实现类型(要遵循的良好做法)。
将实现类型取消导出并仅导出接口也是一种很好的做法,因此实现名称将为NewFooBarEntity()
,entityImpl
,fooImpl
,{{1 }}
一些值得一试的相关问题
What is the idiomatic way in Go to create a complex hierarchy of structs?
is it possible to call overridden method from parent struct in golang?
答案 1 :(得分:0)
在我看来,在一个结构中有三个ID,依赖于它们的方法在语义上是不正确的。为了不模棱两可,你应该写一些更多的代码。比如像这样的东西
type Baz struct {
EntityModel
Foo []*Foo
Bar []*Bar
}
func (b Baz) LinkFoo() {
(&FooLinkerEntity{b.EntityModel, b.Foo}).LinkFoo()
}
func (b Baz) LinkBar() {
(&BarLinkerEntity{b.EntityModel, b.Bar}).LinkBar()
}