确保嵌入式结构实现接口而不会引起歧义 [英] Ensuring embedded structs implement interface without introducing ambiguity

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

问题描述

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

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

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

使用FooBar:

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

输出:

Foo 1
Bar 2

FooBarEntity

现在让我们看一个真实"实体,它是一个Entity(实现为Entity),并且具有FooBar提供的功能:

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不需要知道(不使用)EntityFooBar实现的内部(在我们的情况下为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

游乐场上尝试使用此变体.

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

优良作法是不导出实现类型,而仅导出接口,因此实现名称为entityImplfooImplbarImplfooBarEntityImpl.

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.

一些相关问题值得一查

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

可以打电话吗在golang中从父结构覆盖的方法?

嵌入式struct方法可以了解父/子吗?

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

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

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