结构类型作为映射键

时间:2018-08-11 14:29:41

标签: go types interface go-map

我们具有以下功能:

func (h *Handler) Handle(message interface{}) error {
    //here there is a switch for different messages
    switch m := message.(type) {
    }
}

此签名已给出,无法更改。处理程序处理的消息类型大约有20种。

现在,其中一些消息(大约4条)需要特殊的后处理。在不同的包装中。

因此,我正在考虑这样做:

 func (h *Handler) Handle(message interface{}) error {
        //here there is a switch for different messages

        switch m := message.(type) {
        }
        //only post-process if original message processing succeeds
        postProcessorPkg.Process(message)
    }

现在,在Process函数中,如果消息类型确实是我们需要后处理的消息类型,我想快速查找。我不想在这里再次做switch。在不同的程序包中有许多处理程序,它们具有不同数量的消息类型,并且应该是通用的。

因此,我正在考虑在后处理器中注册消息类型,然后进行查找:

func (p *Postprocessor) Register(msgtype interface{}) {
     registeredTypes[msgtype] = msgtype
}

然后

func (p *Postprocessor) Process(msgtype interface{}) error {
     if ok := registeredTypes[msgtype]; !ok {
        return errors.New("Unsupported message type")
     }
     prop := GetProp(registeredTypes[msgtype])
     doSmthWithProp(prop)
}

这一切现在都不起作用,因为据我所知,我只能“注册”消息的实例,而不能“注册”消息类型本身。因此,映射将仅匹配消息的特定实例,而不匹配我需要的消息类型。

所以我想这需要重新设计。我可以完全放弃注册和地图查找,但是

  • 我无法将Handle函数更改为特定类型(签名必须保留message interface{}
  • 我想避免不得不使用reflect,因为我将很难与一些同事一起捍卫这种解决方案。

2 个答案:

答案 0 :(得分:2)

由于无法将类型设置为映射键,因此我最终决定实施以下基于@Chrono Kitsune解决方案的解决方案:

type Postprocess interface {
    NeedsPostprocess() bool
}

type MsgWithPostProcess struct {}

func (p *MsgWithPostProcess) NeedsPostprocess() bool {
  return true
}

type Msg1 struct {
   MsgWithPostProcess
   //other stuff
}

type Msg2 struct {
    MsgWithPostProcess
    //other stuff
}

type Msg3 struct {
    //no postprocessing needed
}

func (p *Postprocessor) Process(msgtype interface{}) error {
     if _, ok := msgtype.(Postprocess); ok {
        //do postprocessing
     }         
}

在我做的简单测试中,只有Msg1Msg2会被后处理,而Msg3不会被后处理。

答案 1 :(得分:0)

这个问题是我在Google上找到的第一个匹配项,但标题有些误导。因此,我将其留在此处,以便在考虑问题标题的情况下考虑一下。

首先,地图的问题在于其键必须是不可变的值。例如,这就是为什么无法使用切片的原因。切片是指向可变数据的某种指针,因此是不允许的。出于相同的原因,您可以使用数组(固定大小的切片),但不能使用指向数组的指针。

第二,您可以在reflect.TypeOf(...).String()中获得一种类型的规范字符串表示形式。除非您包含程序包路径,否则它并不是明确的,如您在此处看到的。

package main

import (
    "fmt"
    s2 "go/scanner"
    "reflect"
    s1 "text/scanner"
)

type X struct{}

func main() {
    fmt.Println(reflect.TypeOf(1).String())
    fmt.Println(reflect.TypeOf(X{}).String())
    fmt.Println(reflect.TypeOf(&X{}).String())
    fmt.Println(reflect.TypeOf(s1.Scanner{}).String())
    fmt.Println(reflect.TypeOf(s2.Scanner{}).String())
    fmt.Println(reflect.TypeOf(s1.Scanner{}).PkgPath(), reflect.TypeOf(s1.Scanner{}).String())
    fmt.Println(reflect.TypeOf(s2.Scanner{}).PkgPath(), reflect.TypeOf(s2.Scanner{}).String())
}
int
main.X
*main.X
scanner.Scanner
scanner.Scanner
text/scanner scanner.Scanner
go/scanner scanner.Scanner

https://play.golang.org/p/NLODZNdik6r

有了这些信息,您可以(如果觉得很倾斜)创建一张地图,让它从reflect.Type到钥匙,再像这样返回。

package main

import (
    "fmt"
    s2 "go/scanner"
    "reflect"
    s1 "text/scanner"
)

type TypeMap struct {
    m []reflect.Type
}

func (m *TypeMap) Get(t reflect.Type) int {
    for i, x := range m.m {
        if x == t {
            return i
        }
    }
    m.m = append(m.m, t)
    return len(m.m) - 1
}

func (m *TypeMap) Reverse(t int) reflect.Type {
    return m.m[t]
}

type X struct{}

func main() {
    var m TypeMap

    fmt.Println(m.Get(reflect.TypeOf(1)))
    fmt.Println(m.Reverse(0))

    fmt.Println(m.Get(reflect.TypeOf(1)))
    fmt.Println(m.Reverse(0))

    fmt.Println(m.Get(reflect.TypeOf(1)))
    fmt.Println(m.Reverse(0))

    fmt.Println(m.Get(reflect.TypeOf(X{})))
    fmt.Println(m.Reverse(1))

    fmt.Println(m.Get(reflect.TypeOf(&X{})))
    fmt.Println(m.Reverse(2))

    fmt.Println(m.Get(reflect.TypeOf(s1.Scanner{})))
    fmt.Println(m.Reverse(3).PkgPath(), m.Reverse(3))

    fmt.Println(m.Get(reflect.TypeOf(s2.Scanner{})))
    fmt.Println(m.Reverse(4).PkgPath(), m.Reverse(4))
}
0
int
0
int
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner

在上述情况下,我假设N很小。还要注意使用reflect.TypeOf的身份,它将在后续调用中返回相同类型的相同指针。

如果N不小,您可能需要做一些更复杂的事情。

package main

import (
    "fmt"
    s2 "go/scanner"
    "reflect"
    s1 "text/scanner"
)

type PkgPathNum struct {
    PkgPath string
    Num     int
}

type TypeMap struct {
    m map[string][]PkgPathNum
    r []reflect.Type
}

func (m *TypeMap) Get(t reflect.Type) int {
    k := t.String()

    xs := m.m[k]

    pkgPath := t.PkgPath()
    for _, x := range xs {
        if x.PkgPath == pkgPath {
            return x.Num
        }
    }

    n := len(m.r)
    m.r = append(m.r, t)
    xs = append(xs, PkgPathNum{pkgPath, n})

    if m.m == nil {
        m.m = make(map[string][]PkgPathNum)
    }
    m.m[k] = xs

    return n
}

func (m *TypeMap) Reverse(t int) reflect.Type {
    return m.r[t]
}

type X struct{}

func main() {
    var m TypeMap

    fmt.Println(m.Get(reflect.TypeOf(1)))
    fmt.Println(m.Reverse(0))

    fmt.Println(m.Get(reflect.TypeOf(X{})))
    fmt.Println(m.Reverse(1))

    fmt.Println(m.Get(reflect.TypeOf(&X{})))
    fmt.Println(m.Reverse(2))

    fmt.Println(m.Get(reflect.TypeOf(s1.Scanner{})))
    fmt.Println(m.Reverse(3).PkgPath(), m.Reverse(3))

    fmt.Println(m.Get(reflect.TypeOf(s2.Scanner{})))
    fmt.Println(m.Reverse(4).PkgPath(), m.Reverse(4))
}
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner

https://play.golang.org/p/2fiMZ8qCQtY

请注意类型指针的字幕,X*X实际上是不同的类型。