确保嵌入式结构在不引入歧义的情况下实现接口 [英] Ensuring embedded structs implement interface without introducing ambiguity

查看:18
本文介绍了确保嵌入式结构在不引入歧义的情况下实现接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 实现实体接口.

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 级别都支持称为 embedding 的类似结构,并且它确实具有 方法.

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.

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

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 和 Bar

在您的示例中,FooLinkerEntityBarLinkerEntity 只是 decorators,因此它们不需要嵌入 (extend 在 OOP 中)Entity,它们的实现不需要嵌入 EntityImpl.但是,由于我们要使用 Entity.Id() 方法,我们需要一个 Entity 值,它可能是也可能不是 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()) }

使用 FooBar:

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

输出:

Foo 1
Bar 2

FooBarEntity

现在让我们看一个真实"的实体,它是一个 Entity(实现了 Entity)并且具有 FooFoo 提供的特性代码>条形:

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 的内部结构,FooBar 实现(EntityImplFooImplBarImpl 在我们的例子中),我们可以选择只嵌入接口而不嵌入实现(但是在这种情况下我们不能调用 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

Go Playground 上试试这个变体.

Try this variant on the Go Playground.

注意,在创建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.

一些值得一试的相关问题

什么在 Go 中创建复杂的结构层次结构的惯用方法是什么?

是否可以调用golang中父结构的重写方法?

嵌入式结构体方法可以知道父/子吗?

转到嵌入式结构调用子方法而不是父方法

这篇关于确保嵌入式结构在不引入歧义的情况下实现接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆