英文:
Go maps and slices with mixed struct types
问题
我正在尝试学习如何使用Go语言创建一个简单的事件存储器,并具备创建投影的能力。我在处理包含不同类型结构体的切片和映射时遇到了困难。这样做的目的是希望开发人员能够根据需要创建实现IEntity和IEvent接口的多个结构体,这些结构体具有不同的字段。
我之前主要使用JavaScript/Node.js,对C/C++/Java也有一些基础知识。我可能在这里过于追求类/继承模式,需要一些帮助来了解如何在Go中实现相同的功能。
以下是要翻译的代码:
package main
import (
"sync"
"time"
uuid "github.com/satori/go.uuid"
)
// IEntity描述一个实体,即按照时间顺序解析的所有事件的总和
type IEntity interface {
}
// IProjection描述投影结构的接口
type IProjection interface {
Lock()
Unlock()
Get(id uuid.UUID) IEntity
Set(id uuid.UUID, entity IEntity)
GetLastEventTime() time.Time
Append(event IEvent)
}
// IEvent描述事件的接口,系统中添加的任何事件都需要实现此接口
type IEvent interface {
Resolve(projection IProjection)
}
// EventVault是保存所有事件并允许创建投影的基本结构体
type EventVault struct {
sync.Mutex
events []IEvent
}
// Append将IEvent添加到投影中,并运行该事件的IEvent.Resolve方法
func (vault *EventVault) Append(event IEvent) {
vault.Lock()
defer vault.Unlock()
vault.events = append(vault.events, event)
}
// Project使用所有事件创建一个投影,直到选择的结束时间为止
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计算并存储附加到其中的事件的投影
type Projection struct {
sync.Mutex
events []IEvent
entities map[uuid.UUID]IEntity
}
// Get根据ID从投影中返回一个IEntity结构体(用于IEvent.Resolve方法)
func (projection *Projection) Get(id uuid.UUID) IEntity {
return projection.entities[id]
}
// Set将IEntity添加到投影中(用于IEvent.Resolve方法)
func (projection *Projection) Set(id uuid.UUID, entity IEntity) {
projection.entities[id] = entity
}
// GetLastEventTime返回最后添加的事件的时间
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将IEvent添加到投影中,并运行该事件的IEvent.Resolve方法
func (projection *Projection) Append(event IEvent) {
projection.Lock()
projection.events = append(projection.events, event)
event.Resolve(projection)
projection.Unlock()
}
// ------------------ 以下是上述系统的使用示例 ------------------
// PlayerEntity是一个可用于测试的示例实体
type PlayerEntity struct {
ID uuid.UUID
Name string
Birthday time.Time
UpdatedAt time.Time
}
// AddPlayerEvent是一个可用于测试的示例事件
type AddPlayerEvent struct {
ID uuid.UUID
Time time.Time
Name string
}
// Resolve将创建一个新的PlayerEntity并将其添加到投影中
func (event *AddPlayerEvent) Resolve(projection IProjection) {
player := PlayerEntity{ID: uuid.NewV4(), Name: event.Name, UpdatedAt: event.Time}
projection.Set(player.ID, &player)
}
// SetBirthdayPlayerEvent是一个可用于测试的示例事件
type SetBirthdayPlayerEvent struct {
ID uuid.UUID
Time time.Time
Birthday time.Time
}
// Resolve将更改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)
结构体类型可能丢失,因此我需要另一种方法来存储它们以便将它们放入映射和切片中,但是如何实现呢?
英文:
I'm trying to learn how to work with Go by creating a simple event store with the ability to create projections. I get stuck on how to work with slices and maps that contains structs of mixed types. The point with this would be that I want a developer to create as many structs implementing IEntity and IEvent as needed with various fields.
I come from a JavaScript/Node.js background with some basic knowledge of C/C++/Java and I'm probably looking to much for class/inheritance patterns here and need some help on how to get the same functionality in 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)
}
The errors I get are
./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)
The struct types are probably lost so I need another way of storing them to put them into the maps and slices but how?
答案1
得分: 1
在Go语言中,当你声明一个接口时,比如:
type IEntity interface {
}
你定义了可以在该接口中进行的所有操作,而无需进行类型转换。所以在这里,你没有为该接口定义任何功能。如果你想要功能,你需要给它添加一个方法,比如:
type IEntity interface {
Time() time.Time
}
任何想要与该接口一起使用的类型都必须实现这些函数,例如:
func (a AddPlayerEvent) Time() time.Time {
return a.Time
}
然后你可以使用这些方法之一:
func (projection *Projection) Append(event IEvent) {
...
event.Time()
...
}
还有两个注意事项:
- 当遍历一个map时,你可以使用
k, v := range my_map
。 *projection.Get(event.ID)
试图解引用一个非指针类型。
英文:
In golang when you declare an interface like
type IEntity interface {
}
You define everything that can be done with this interface without a type cast. So here you have defined no functionality for this interface. If you want functionality than you have to give it a method like
type IEntity interface {
Time() time.Time
}
Any type that wants to be used with that interface must implement those functions i.e.
func (a AddPlayerEvent) Time() time.Time {
return a.Time
}
Then you can use any of those methods
func (projection *Projection) Append(event IEvent) {
...
event.Time()
...
}
2 more notes
- When iterating through a map you can use k, v := range my_map
- *projection.Get(event.ID) attempts to deference a non pointer type
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论