我正在尝试通过更好地定义接口和使用嵌入式结构来重用功能来清理我的代码库。在我的例子中,我有许多可以链接到各种对象的实体类型。我想定义捕获实现接口的需求和结构的接口,然后可以将接口嵌入到实体中。
// 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接口。
// 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()
?有没有办法在编译时而不是运行时进行此检查?
去是not (quite) an object oriented language:它没有类,它有does not have type inheritance;但它支持一个类似的构造,称为嵌入在struct
级别和interface
级别,它确实有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中扩展)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
,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()
它的输出:
Foo 3
Bar 3
FooBar 3
在Go Playground上尝试这个变种。
请注意,在创建FooBarEntityImpl
时,Entity
的值将用于多个复合文字。由于我们只创建了一个Entity
(EntityImpl
)并且我们在所有地方都使用了它,因此在不同的实现类中只使用了一个id,只有一个“引用”传递给每个结构,而不是复制/副本。这也是预期/必需的用法。
由于FooBarEntityImpl
创建非常重要且容易出错,因此建议创建类似构造函数的函数:
func NewFooBarEntity(id int) FooBarEntity {
e := &EntityImpl{id}
return &FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
}
请注意,工厂函数NewFooBarEntity()
返回接口类型的值而不是实现类型(要遵循的良好做法)。
将实现类型取消导出并仅导出接口也是一种很好的做法,因此实现名称将为entityImpl
,fooImpl
,barImpl
,fooBarEntityImpl
。
一些值得一试的相关问题
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?
在我看来,在一个结构中有三个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()
}