为了账号安全,请及时绑定邮箱和手机立即绑定

确保嵌入式结构在不引入歧义的情况下实现接口

确保嵌入式结构在不引入歧义的情况下实现接口

Go
繁花如伊 2022-01-04 20:47:19
我试图通过更好地定义接口和使用嵌入式结构来重用功能来清理我的代码库。就我而言,我有许多可以链接到各种对象的实体类型。我想定义捕获需求的接口和实现接口的结构,然后可以将其嵌入到实体中。// All entities implement this interfacetype Entity interface {  Identifier()  Type()}// Interface for entities that can link Foostype 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 Barstype 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 interfacetype 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()and 中LinkBar()进行类型断言以到达Identifier()and Type()?有什么方法可以在编译时而不是运行时进行此检查吗?
查看完整描述

2 回答

?
慕哥9229398

TA贡献1877条经验 获得超6个赞

Go不是(完全)面向对象的语言:它没有类,也没有类型继承;但它支持一个名为类似的结构嵌入无论在struct级别和interface水平,它也有方法。


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


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


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


实体

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


type Entity interface {

    Id() int

}


type EntityImpl struct{ id int }


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

Foo 和 Bar

在您的示例中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

FooBar实体

现在,让我们看到一个“真实”的实体,这是一个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

FooBarEntity 第 2 轮

如果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上试试这个变体。


FooBarEntity 创建

请注意,在创建时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。


查看完整回答
反对 回复 2022-01-04
?
繁星coding

TA贡献1797条经验 获得超4个赞

在我看来,在一个结构中拥有三个 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()

}


查看完整回答
反对 回复 2022-01-04
  • 2 回答
  • 0 关注
  • 113 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信