我有一个解析日志文件的系统,该日志文件包含mysql表的变更集,可以想象像binlog这样的东西。可以有更新和插入,删除我们现在忽略。我的模块的功能得到如下输入:
type Changeset struct {
Table string // which table was affected
Type string // INSERT or UPDATE
OldData map[string]string // these 2 fields contain all columns of a table row
NewData map[string]string
}
OldData
在INSERT
变更集时为空,当变为UPDATE
变更集时,OldData
和NewData
被填充(数据前后的数据)更新)。
现在我不想在我的模块中使用这样的无类型数据,因为我需要建模一些域,并且有一些类型安全性会更好。但是,如果更改是该域逻辑的插入或更新,我仍然需要保留知识(例如,如果它是更新,我将验证某些字段没有更改,作为示例)。
假设我有两个表(假设它们只有一个名为Id的字段,但实际上它们有更多不同的字段)。所以我像这样建模这些对象:
type Foo struct { // foo table
Id string
// ... imagine more fields here ...
}
type Bar struct { // bar table
Id string
// ... imagine more fields here ...
}
现在我可以从map[string][string]
和Changeset.OldData
映射Changeset.NewData
,但是如果更改是插入或更新,我就不知道了。我有点来回思考,但我想出的最好的是:
type FooInsert struct {
New Foo
}
type FooUpdate struct {
New Foo
Old Foo
}
type BarInsert struct {
New Bar
}
type BarUpdate struct {
New Bar
Old Bar
}
映射代码如下所示:
func doMap(c Changeset) interface{} {
if c.Table == "foo" {
switch c.Type {
case "UPDATE":
return FooUpdate{Old: Foo{Id: c.OldData["id"]}, New: Foo{Id: c.NewData["id"]}}
case "INSERT":
return FooInsert{New: Foo{Id: c.NewData["id"]}}
}
}
if c.Table == "bar" {
switch c.Type {
// ... almost same as above, but return BarUpdate/BarInsert ...
}
}
return nil
}
好处是,它使我能够在这个映射函数的结果上写一个类型开关,如下所示:
insertChangeset := Changeset{
Table: "foo",
Type: "INSERT",
NewData: map[string]string{"id": "1"},
}
o := doMap(insertChangeset)
switch o.(type) {
case BarUpdate:
println("Got an update of table bar")
case FooUpdate:
println("Got an update of table foo")
case BarInsert:
println("Got an insert to table bar")
case FooInsert:
println("Got an insert to table foo")
}
typeswitch是我最终需要的(每个更改变更集类型和每个实体的不同类型。)但是:
doMap
中显示的映射代码非常难看且重复。X
,我需要创建另外两种类型XInsert
和XUpdate
。这个烂摊子有什么办法吗?在其他编程语言中,我可能会想到类似的东西:
type Update<T> {
T Old
T New
}
type Insert<T> {
T New
}
但不确定如何在Go中对此进行建模。我还创建了一个游乐场示例,在一个程序中显示整个代码:https://play.golang.org/p/ZMnB5K7RaI
答案 0 :(得分:0)
看看this solution。这是一种可能的解决方案。
通常:您希望在此处使用接口。在示例中,我使用接口DataRow
来存储任何表的行的数据。所有表结构必须实现2个函数,如我的示例所示。 (另请参阅我关于使用泛型的基类中的常规函数的说明)
这里的代码再次出现:
package main
import "fmt"
type Foo struct {
Id string
}
func (s *Foo) Fill(m map[string]string) {
// If you want to build a general Fill you can build a base struct for Foo, Bar, etc. that works with reflect.
// Note that it will be slower than implementing the function here! Ask me if you want one I built recently.
s.Id = m["id"]
}
func (s *Foo) GetRow() interface{} {
return nil
}
type Bar struct {
Id string
}
func (s *Bar) Fill(m map[string]string) {
s.Id = m["id"]
}
func (s *Bar) GetRow() interface{} {
return nil
}
type DataRow interface {
Fill(m map[string]string)
GetRow() interface{}
}
type Changeset struct {
Table string
Type string
OldData map[string]string
NewData map[string]string
}
type ChangesetTyped struct {
Table string
Type string
OldData DataRow
NewData DataRow
}
func doMap(c Changeset) ChangesetTyped {
ct := ChangesetTyped{
Table: c.Table,
Type: c.Type,
OldData: parseRow(c.Table, c.OldData),
}
if c.Type == "UPDATE" {
ct.NewData = parseRow(c.Table, c.NewData)
}
return ct
}
func parseRow(table string, data map[string]string) (row DataRow) {
if table == "foo" {
row = &Foo{}
} else if table == "bar" {
row = &Bar{}
}
row.Fill(data)
return
}
func main() {
i := Changeset{
Table: "foo",
Type: "INSERT",
NewData: map[string]string{"id": "1"},
}
u1 := Changeset{
Table: "foo",
Type: "UPDATE",
OldData: map[string]string{"id": "20"},
NewData: map[string]string{"id": "21"},
}
u2 := Changeset{
Table: "bar",
Type: "UPDATE",
OldData: map[string]string{"id": "30"},
NewData: map[string]string{"id": "31"},
}
m1 := doMap(i)
m2 := doMap(u1)
m3 := doMap(u2)
fmt.Println(m1, m1.OldData)
fmt.Println(m2, m2.OldData, m2.NewData)
fmt.Println(m3, m3.OldData, m3.NewData)
}
如果要将实际行从DataRow
转换为正确的类型使用(在此示例中为Foo类型):
foo, ok := dt.GetRow().(Foo)
if !ok {
fmt.Println("it wasn't of type Foo after all")
}
希望这可以帮助你进行golang任务!