Go 结构类型作为映射键

Go 结构类型作为映射键,go,types,interface,go-map,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) Han

我们有以下功能:

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
函数中,我想快速查找消息类型是否确实是我们需要进行后处理的类型。我不想在这里再次进行
切换。在不同的包中有许多处理程序,具有不同数量的消息类型,并且应该是通用的

所以我想在后处理器中注册消息类型,然后进行查找:

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)
}
这一切现在都不起作用,因为据我所知,我只能“注册”消息的实例,而不能注册消息类型本身。因此,映射将只匹配消息的特定实例,而不是其类型,这正是我所需要的

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

  • 我无法将
    句柄
    函数更改为特定类型(签名需要保留
    消息接口{}
  • 我希望避免不得不使用
    reflect
    ,因为我很难与一些同事一起为这样的解决方案辩护

由于无法将类型设置为映射键,我最终决定实施以下基于@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
     }         
}

在我做的简单测试中,只有
Msg1
Msg2
将被后置处理,而不是
Msg3
,这正是我想要的。

这个问题是我在谷歌上发现的第一个热门问题,但标题有点误导。因此,我将把这个问题留在这里,以供思考

首先,映射的问题是它的键必须是不可变的值。这就是为什么不能使用片作为映射键。片是指向可变数据的排序指针,因此不允许使用。出于同样的原因,可以使用数组(固定大小的片),但不能使用指向数组的指针

其次,在
reflect.TypeOf(…).String()
中有一种方法可以获得类型的规范字符串表示形式。但是,除非包含包路径,否则它不是明确的,正如您在这里看到的那样

主程序包
进口(
“fmt”
s2“开始/扫描”
“反映”
s1“文本/扫描仪”
)
类型X结构{}
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())
}

有了这些信息,您可以(如果您愿意的话)创建一个映射,让我们从一个
reflect.Type
到一个键,然后再返回,如下所示

主程序包
进口(
“fmt”
s2“开始/扫描”
“反映”
s1“文本/扫描仪”
)
类型映射结构{
m.Type
}
func(m*TypeMap)Get(t reflect.Type)int{
对于i,x:=范围m.m{
如果x==t{
返回i
}
}
m、 m=附加(m.m,t)
回程长度(m.m)-1
}
func(m*TypeMap)Reverse(t int)reflect.Type{
返回m.m[t]
}
类型X结构{}
func main(){
var m类型映射
fmt.Println(m.Get(reflect.TypeOf(1)))
格式打印LN(m.反向(0))
fmt.Println(m.Get(reflect.TypeOf(1)))
格式打印LN(m.反向(0))
fmt.Println(m.Get(reflect.TypeOf(1)))
格式打印LN(m.反向(0))
fmt.Println(m.Get(reflect.TypeOf(X{})))
fmt.打印LN(m.反向(1))
fmt.Println(m.Get(reflect.TypeOf(&X{})))
fmt.打印LN(m.反向(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))
}
在上面的例子中,我假设
N
很小。还要注意使用
reflect.TypeOf
的标识,它将在后续调用中返回相同类型的相同指针

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

主程序包
进口(
“fmt”
s2“开始/扫描”
“反映”
s1“文本/扫描仪”
)
类型PkgPathNum struct{
PkgPath字符串
Num int
}
类型映射结构{
m映射[string][]PkgPathNum
r.Type
}
func(m*TypeMap)Get(t reflect.Type)int{
k:=t.String()
xs:=m.m[k]
pkgPath:=t.pkgPath()
对于ux:=范围xs{
如果x.PkgPath==PkgPath{
返回x.Num
}
}
n:=len(m.r)
m、 r=附加(m.r,t)
xs=append(xs,PkgPathNum{pkgPath,n})
如果m.m==nil{
m、 m=make(映射[string][]PkgPathNum)
}
m、 m[k]=xs
返回n
}
func(m*TypeMap)Reverse(t int)reflect.Type{
返回m.r[t]
}
类型X结构{}
func main(){
var m类型映射
fmt.Println(m.Get(reflect.TypeOf(1)))
格式打印LN(m.反向(0))
fmt.Println(m.Get(reflect.TypeOf(X{})))
fmt.打印LN(m.反向(1))
fmt.Println(m.Get(reflect.TypeOf(&X{})))
fmt.打印LN(m.反向(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))
}


注意类型指针的副标题,
X
*X
实际上是不同的类型。

如果你想要类型的映射,我认为没有办法避免使用
reflect
。顺便说一句,当你制作这样一个只有键才重要的映射时,我推荐我们
int
main.X
*main.X
scanner.Scanner
scanner.Scanner
text/scanner scanner.Scanner
go/scanner scanner.Scanner
0
int
0
int
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner
0
int
1
main.X
2
*main.X
3
text/scanner scanner.Scanner
4
go/scanner scanner.Scanner