oop - 确保嵌入式结构实现接口(interface)而不引入歧义

标签 oop go interface composition embedding

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

// 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 接口(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 的类型来说,这最终会导致歧义错误。

// Baz.Identifier() is ambiguous between EntityModel, FooLinkerEntity,
// and BarLinkerEntity.
type Baz struct {
    EntityModel
    FooLinkerEntity
    BarLinkerEntity
}

构建此类代码的正确 Go 方式是什么?我是否只需在 LinkFoo()LinkBar() 中进行类型断言即可到达 Identifier()Type()?有没有办法在编译时而不是运行时进行此检查?

最佳答案

Go 是 not (quite) an object oriented language :它没有类,并且它 does not have type inheritance ;但它在 struct 上都支持名为嵌入的类似构造。水平和 interface级别,并且它确实有 methods .

所以你应该停止用面向对象编程的方式思考,而开始用组合的方式思考。既然你在评论中说 FooLinkerEntity永远不会单独使用,这有助于我们以干净的方式实现您想要的。

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

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

实体

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

type Entity interface {
    Id() int
}

type EntityImpl struct{ id int }

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

美食和酒吧

在您的示例中 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 )并具有 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 的内部结构, FooBar实现(在我们的例子中 EntityImplFooImplBarImpl ),我们可以选择仅嵌入接口(interface)而不嵌入实现(但在这种情况下,我们不能调用 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()返回接口(interface)类型的值而不是实现类型(要遵循的良好实践)。

不导出实现类型并仅导出接口(interface)也是一个好习惯,因此实现名称将为 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?

Can embedded struct method have knowledge of parent/child?

Go embedded struct call child method instead parent method

关于oop - 确保嵌入式结构实现接口(interface)而不引入歧义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36710259/

相关文章:

c++ - C++是否使用接口(interface)?

php - 我们可以在 php 类的一个方法中使用两个 mysql 查询吗?

java - 接口(interface)应该放在单独的包中吗?

javascript - 设计在 DOM 中表示 UI 对象的 JavaScript 类的最佳实践是什么?

go - 使用 client-go 观看 CustomResourceDefinitions (CRD)

go - 如何将字符串转换为 C 语言中的 ASCII 字符串?

go - 如何在中间件go-chi中获取路由

java - 'instanceof' 运算符对接口(interface)和类的行为不同

inheritance - 在 TypeScript 中,一个接口(interface)可以扩展一个类,这是为了什么?

java工厂和观察者模式