Тип структуры как ключ карты
У нас есть следующая функция:
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
на самом деле это разные типы.