确保嵌入式结构实现接口而不会引入歧义

问题描述 投票:1回答:2

我正在尝试通过更好地定义接口和使用嵌入式结构来重用功能来清理我的代码库。在我的例子中,我有许多可以链接到各种对象的实体类型。我想定义捕获实现接口的需求和结构的接口,然后可以将接口嵌入到实体中。

// 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()?有没有办法在编译时而不是运行时进行此检查?

oop go interface composition embedding
2个回答
2
投票

去是not (quite) an object oriented language:它没有类,它有does not have type inheritance;但它支持一个类似的构造,称为嵌入在struct级别和interface级别,它确实有methods

所以你应该停止在OOP中思考并开始考虑构图。既然你在评论中说FooLinkerEntity永远不会单独使用,这有助于我们以干净的方式实现你想要的。

我将使用新名称和较少的功能来专注于问题和解决方案,这将导致更短的代码,也更容易理解。

完整的代码可以在Go Playground上查看和测试。

Entity

简单的Entity及其实现将如下所示:

type Entity interface {
    Id() int
}

type EntityImpl struct{ id int }

func (e *EntityImpl) Id() int { return e.id }

Foo and Bar

在你的例子中,FooLinkerEntityBarLinkerEntity只是装饰器,因此它们不需要嵌入(在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()) }

使用FooBar

f := FooImpl{&EntityImpl{1}}
f.SayFoo()
b := BarImpl{&EntityImpl{2}}
b.SayBar()

输出:

Foo 1
Bar 2

FooBarEntity

现在让我们看一个“真实的”实体,它是一个Entity(实现Entity)并且具有FooBar提供的两个特性:

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 round #2

如果FooBarEntityImpl不需要知道(不使用)EntityFooBar实现的内部(在我们的例子中为EntityImplFooImplBarImpl),我们可能选择仅嵌入接口而不是实现(但在此我们不能调用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上尝试这个变种。

FooBarEntity creation

请注意,在创建FooBarEntityImpl时,Entity的值将用于多个复合文字。由于我们只创建了一个EntityEntityImpl)并且我们在所有地方都使用了它,因此在不同的实现类中只使用了一个id,只有一个“引用”传递给每个结构,而不是复制/副本。这也是预期/必需的用法。

由于FooBarEntityImpl创建非常重要且容易出错,因此建议创建类似构造函数的函数:

func NewFooBarEntity(id int) FooBarEntity {
    e := &EntityImpl{id}
    return &FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
}

请注意,工厂函数NewFooBarEntity()返回接口类型的值而不是实现类型(要遵循的良好做法)。

将实现类型取消导出并仅导出接口也是一种很好的做法,因此实现名称将为entityImplfooImplbarImplfooBarEntityImpl


一些值得一试的相关问题

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?

Can embedded struct method have knowledge of parent/child?

Go embedded struct call child method instead parent method


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()
}
© www.soinside.com 2019 - 2024. All rights reserved.