【问题标题】:Go maps and slices with mixed struct types使用混合结构类型的 Go 映射和切片
【发布时间】:2017-08-12 23:10:50
【问题描述】:

我正在尝试通过创建一个能够创建投影的简单事件存储来学习如何使用 Go。我被困在如何使用包含混合类型结构的切片和映射上。这样做的重点是,我希望开发人员根据需要使用各种字段创建尽可能多的实现 IEntity 和 IEvent 的结构。

我来自 JavaScript/Node.js 背景,具有一些 C/C++/Java 的基本知识,我可能在这里寻找很多类/继承模式,并且需要一些关于如何在 Go 中获得相同功能的帮助.

package main

import (
    "sync"
    "time"

    uuid "github.com/satori/go.uuid"
)

// IEntity describes an entity, a struct that is the sum of all events resolved in a chronological order
type IEntity interface {
}

// IProjection describes the interface for a projection struct
type IProjection interface {
    Lock()
    Unlock()
    Get(id uuid.UUID) IEntity
    Set(id uuid.UUID, entity IEntity)
    GetLastEventTime() time.Time
    Append(event IEvent)
}

// IEvent describes the interface for a event, any event added to the system needs to implement this interface
type IEvent interface {
    Resolve(projection IProjection)
}

// EventVault is the base struct that keeps all the events and allows for projections to be created
type EventVault struct {
    sync.Mutex
    events []IEvent
}

// Append adds a IEvent to the projection and runs that events IEvent.Resolve method
func (vault *EventVault) Append(event IEvent) {
    vault.Lock()
    defer vault.Unlock()
    vault.events = append(vault.events, event)
}

// Project creates a projection with entities from all events up until the choosen end time
func (vault *EventVault) Project(endTime time.Time, projection IProjection) IProjection {
    lastEventTime := projection.GetLastEventTime()
    for index := range vault.events {
        event := vault.events[index]
        if event.Time.After(lastEventTime) && event.Time.Before(endTime) {
            projection.Append(event)
        }
    }
    return projection
}

// Projection caculates and stores a projection of the events appended to it with the Append method
type Projection struct {
    sync.Mutex
    events   []IEvent
    entities map[uuid.UUID]IEntity
}

// Get returns an IEntity struct from an id (for use in the IEvent.Resolve method)
func (projection *Projection) Get(id uuid.UUID) IEntity {
    return projection.entities[id]
}

// Set add a IEntity to the projection (for use in the IEvent.Resolve method)
func (projection *Projection) Set(id uuid.UUID, entity IEntity) {
    projection.entities[id] = entity
}

// GetLastEventTime returns the time for the event that was added last
func (projection *Projection) GetLastEventTime() time.Time {
    event := projection.events[len(projection.events)-1]

    if event == nil {
        return time.Unix(0, 0)
    }

    return event.Time
}

// Append adds a IEvent to the projection and runs that events IEvent.Resolve method
func (projection *Projection) Append(event IEvent) {
    projection.Lock()
    projection.events = append(projection.events, event)
    event.Resolve(projection)
    projection.Unlock()
}

// ------------------ Below is usage of above system ------------------

// PlayerEntity is a sample entity that can be used for testing
type PlayerEntity struct {
    ID        uuid.UUID
    Name      string
    Birthday  time.Time
    UpdatedAt time.Time
}

// AddPlayerEvent is a sample event that can be used for testing
type AddPlayerEvent struct {
    ID   uuid.UUID
    Time time.Time
    Name string
}

// Resolve will create a new PlayerEntity and add that to the projection
func (event *AddPlayerEvent) Resolve(projection IProjection) {
    player := PlayerEntity{ID: uuid.NewV4(), Name: event.Name, UpdatedAt: event.Time}
    projection.Set(player.ID, &player)
}

// SetBirthdayPlayerEvent is a sample event that can be used for testing
type SetBirthdayPlayerEvent struct {
    ID       uuid.UUID
    Time     time.Time
    Birthday time.Time
}

// Resolve will change the name on a PlayerEntity
func (event *SetBirthdayPlayerEvent) Resolve(projection IProjection) {
    player := *projection.Get(event.ID)
    player.Birthday = event.Birthday
    player.UpdatedAt = event.Time
}

func main() {
    vault := EventVault{}
    event1 := AddPlayerEvent{ID: uuid.NewV4(), Time: time.Now(), Name: "Lisa"}
    vault.Append(&event1)
    birthday, _ := time.Parse("2006-01-02", "2017-03-04")
    event2 := SetBirthdayPlayerEvent{ID: event1.ID, Time: time.Now(), Birthday: birthday}
    vault.Append(&event2)
}

我得到的错误是

./main.go:47: event.Time undefined (type IEvent has no field or method Time)
./main.go:79: event.Time undefined (type IEvent has no field or method Time)
./main.go:122: invalid indirect of projection.Get(event.ID) (type IEntity)

结构类型可能丢失了,所以我需要另一种存储方式来将它们放入映射和切片中,但是如何?

【问题讨论】:

  • 我们是否应该在屏幕上滑动手指直到我们数到 122 行,或者您想指出问题出在哪里? :)
  • 如果我理解正确的话,你可以将Time() time.Time 添加到你的IEvent 界面。此外,您需要在结构中实现一个名为 Time() time.Time 的方法,并将给定的第 47,79 行更新为 event.Time()
  • 是的,但我看到了两个问题,一个是我必须为所有属性添加一个 getter/setter,另一个是如果我添加一个实体,例如调用具有属性 Kind string、Branches []Branch 等的 TreeEntity,它们将具有不同的 getter/setter,因为它们具有不同的属性,因此会破坏 Resolve 方法。 :/ 我习惯于松散地输入语言 :/

标签: inheritance go data-structures struct interface


【解决方案1】:

在golang中当你声明一个像

这样的接口时
type IEntity interface {
}

您定义了可以使用此接口完成的所有操作,而无需类型转换。所以在这里你没有为这个接口定义任何功能。如果你想要功能而不是你必须给它一个类似的方法

type IEntity interface {
    Time() time.Time
}

任何想要与该接口一起使用的类型都必须实现这些功能,即

func (a AddPlayerEvent) Time() time.Time {
    return a.Time 
} 

See the docs

那么你可以使用其中任何一种方法

func (projection *Projection) Append(event IEvent) {
    ...
    event.Time()
    ...
}

还有 2 条笔记

  • 在遍历地图时,您可以使用 k, v := range my_map
  • *projection.Get(event.ID) 尝试尊重非指针类型

【讨论】:

  • 所以答案是我必须对添加到结构中的所有属性使用 getter/setter,这并不是我希望的答案,但也许这是唯一的方法。如果有另一种方法可以将 slice/map 中的引用保存到混合结构而不需要 getter/setter,即使不需要接口也会很好。我确实尝试过使用 getter/setter,但没有成功,但我想我需要更多练习才能解决这个问题。感谢您的意见。
  • 对,要明确的是,您不必使用接口作为传递给结构中的方法/存储的类型,但如果您要使用接口,则最好使用接口您需要的所有方法。
  • 将 player.Birthday 更改为 setter 要求我将 SetBirthday(time time.Time) 添加到 IEntity ,因为并非所有实体都必须拥有 Birthday 打破了拥有名为 IEntity 的通用接口的完整点. ://
  • 看你的观点,但我想我缺乏如何在单个数组/切片/映射中存储/检索混合结构类型的知识,没有接口不知何故也会非常好
  • 你不能在切片/映射中存储混合类型你必须使用类型断言。
猜你喜欢
  • 2017-07-06
  • 1970-01-01
  • 1970-01-01
  • 2015-05-02
  • 2011-04-08
  • 2021-12-13
  • 1970-01-01
  • 1970-01-01
  • 2017-06-21
相关资源
最近更新 更多