5

我们有以下功能:

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,因为我将很难与一些同事捍卫这样的解决方案。
4

2 回答 2

3

由于无法将类型设置为映射键,因此我最终决定实现以下解决方案,该解决方案基于 @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,这正是我想要的。

于 2018-08-11T16:27:05.383 回答
1

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

首先,maps 的问题是它的键必须是一个可比较的值。这就是为什么例如不能使用切片是映射键的原因。切片是不可比较的,因此是不允许的。出于同样的原因,您可以使用数组(固定大小的切片)但不能使用切片。

其次,您有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

有了这些信息,你可以(如果你愿意的话)创建一个地图,让我们从 areflect.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实际上是不同的类型。

于 2019-03-24T07:53:27.257 回答