Тип структуры как ключ карты

У нас есть следующая функция:

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только потому, что мне будет сложно отстаивать такое решение с некоторыми коллегами.

1 ответ

Поскольку нет возможности установить тип в качестве ключа карты, я, наконец, решил реализовать следующее решение, основанное на решении @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что я и хотел.

Этот вопрос был первым хитом, который я нашел в 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 на самом деле это разные типы.

Другие вопросы по тегам