I have multiple types like this:
type QueryMessage struct {
Header MessageHeader
Type MessageType
Query SqlQuery
}
type UpdateMessage struct {
Header MessageHeader
Type MessageType
OldData map[string]interface{}
NewData map[string]interface{}
}
type InsertMessage struct {
Header MessageHeader
Type MessageType
Data map[string]interface{}
}
They all have two properties in common, Header
and Type
. In the end I need to aggregate them into an array of generic messages. At the moment my Message
interface looks like this:
type Message interface {}
So what I basically do is something like this (casting them all to Message interface):
q := QueryMessage{ ... }
u := UpdateMessage{ ... }
i := InsertMessage{ ... }
allMessages := [3]Message { Message(q), Message(u), Message(i), }
This works, but it loses all the type information and I would like to be able to still expose Header
and Type
from the Message
type (so that client code theoretically could cast the Message
based on the Type
back to it's original type.
How can this be done? I couldn't figure out a proper way, interfaces can't have properties and if I make Message
a struct, Go doesn't allow me to cast e. g. a QueryMessage
to a Message
anymore.
Your interface can provide accessors :
type Message interface {
GetHeader() MessageHeader
GetType() MessageType
}
You then have to implement this interface on all your types.
To factor out the common part, you can use an extra base type, and embed it in your other structs :
// the interface :
type Message interface {
GetHeader() MessageHeader
GetType() MessageType
}
// a base struct type, which holds 2 fields and implements the Message interface :
type baseMessage struct {
Header MessageHeader
Type MessageType
}
func (b *baseMessage) GetHeader() MessageHeader {
return b.Header
}
func (b *baseMessage) GetType() MessageType {
return b.Type
}
// your three types, with an embedded "baseMessage" part :
type QueryMessage struct {
baseMessage
Query SqlQuery
}
type UpdateMessage struct {
baseMessage
OldData map[string]interface{}
NewData map[string]interface{}
}
type InsertMessage struct {
baseMessage
Data map[string]interface{}
}
// compile time check : all the above types implement the Message interface :
var _ Message = &QueryMessage{}
var _ Message = &UpdateMessage{}
var _ Message = &InsertMessage{}
Just an opinion, but considering the fact that Header
and Type
are just fields/data and not methods/behaviour, I'd lose the Message interface
and instead use a Message struct
.
type Message struct {
Header MessageHeader
Type MessageType
Details interface{}
}
And then based on the value of the Type
field you can cast Details
to whatever.
if m.Type == QueryMessageType {
q := m.Details.(SqlQuery)
} else if m.Type == UpdateMessageType {
u := m.Details.(UpdateMessageDetails)
}
Or just use a type switch
switch m.Details.(type) {
case SqlQuery:
// ...
case UpdateMessageDetails:
// ...
}