dqxsuig64994 2016-04-19 06:30 采纳率: 0%
浏览 9

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

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
}

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
}

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
}

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?

  • 写回答

2条回答 默认 最新

  • duanlie4621 2016-04-19 07:34
    关注

    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.

    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

    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()) }
    

    Using Foo and Bar:

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

    Output:

    Foo 1
    Bar 2
    

    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())
    }
    

    Using FooBarEntity:

    e := &EntityImpl{3}
    x := FooBarEntityImpl{e, FooImpl{e}, BarImpl{e}}
    x.SayFoo()
    x.SayBar()
    x.SayFooBar()
    

    Output:

    Foo 3
    Bar 3
    FooBar 3 3 3
    

    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()) }
    

    Its usage is the same:

    e := &EntityImpl{3}
    x := FooBarEntityImpl{e, &FooImpl{e}, &BarImpl{e}}
    x.SayFoo()
    x.SayBar()
    x.SayFooBar()
    

    Its output:

    Foo 3
    Bar 3
    FooBar 3
    

    Try this variant on the Go Playground.

    FooBarEntity creation

    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.

    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}}
    }
    

    Note that the factory function NewFooBarEntity() returns a value of interface type and not the implementation type (good practice to be followed).

    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.


    Some related questions worth checking out

    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

    评论

报告相同问题?

悬赏问题

  • ¥15 Vue3 大型图片数据拖动排序
  • ¥15 划分vlan后不通了
  • ¥15 GDI处理通道视频时总是带有白色锯齿
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)
  • ¥15 自适应 AR 模型 参数估计Matlab程序
  • ¥100 角动量包络面如何用MATLAB绘制
  • ¥15 merge函数占用内存过大
  • ¥15 使用EMD去噪处理RML2016数据集时候的原理
  • ¥15 神经网络预测均方误差很小 但是图像上看着差别太大