根据Go中的字符串动态创建特定类型的变量

时间:2012-06-20 20:32:58

标签: json dynamic types interface go

简易版
如何根据字符串的值创建某种类型的变量?

type ta struct { a int }
type tb struct { b float }
type tc struct { c string }

t := "tb"
v := MagicVarFunc(t) // Returns a new allocated var of type interface{}
v.(tb).b = 8.3

真实的例子
令我惊讶的是,在下面的工作示例中,我基于字符串动态创建变量。这是通过在 map 中注册每个结构类型来完成的, string 是键, type 的nil指针是值。
每个类型使用New()方法实现接口,该方法返回该特定类型的新变量。

下面的示例非常接近我想要做的事情,其中​​每个动作都有一组JSON编码数据,这些数据将填充相应的结构。我构建它的方式也是因为我希望能够创建我注册到地图的新独立动作。

我不确定现在是否滥用这种语言 如果我完全不在意,愿任何人给我任何指示吗?有明显更简单的方法吗?

package main

import (
    "fmt"
    "encoding/json"
)

// All I require of an action is that it may be executed
type ActionHandler interface {
    Exec()
    New() ActionHandler
}

// My list of actions
var mActions = make(map[string]ActionHandler)

// Action Exit (leaving the program)
type aExit struct {}
func (s *aExit) Exec() { fmt.Println("Good bye") }
func (s *aExit) New() ActionHandler { return new(aExit) }
func init() {
    var a *aExit
    mActions[`exit`] = a
}

// Action Say (say a message to someone)
type aSay struct {
    To  string
    Msg string
}
func (s *aSay) Exec() { fmt.Println(`You say, "` + s.Msg + `" to ` + s.To) }
func (s *aSay) New() ActionHandler { return new(aSay) }
func init() {
    var a *aSay
    mActions[`say`] = a
}

func inHandler(action string, data []byte) {
    a := mActions[action].New()
    json.Unmarshal(data, &a)
    a.Exec()
}

func main(){
    inHandler(`say`, []byte(`{"to":"Sonia","msg":"Please help me!"}`))
    inHandler(`exit`, []byte(`{}`))
}

3 个答案:

答案 0 :(得分:6)

如果可以在运行时获取new值,则可以使用反射来获取零值,或者使用反射分配类型的新值(如Type)。但是,我认为没有办法从字符串中获取Type。您需要具有该类型的值才能获得该类型。

我采用了你的想法,使用地图。我将字符串映射到类型本身,您可以使用reflect.TypeOf获取它,它从接口值中获取类型。然后我使用reflect.Zero来获取该类型的零值(对于每种类型都存在一个方便的值)。然后我把价值作为一个界面。

package main
import "reflect"

type ta struct { a int }
type tb struct { b float64 }
type tc struct { c string }

var mActions map[string]reflect.Type = make(map[string]reflect.Type)
func init() {
  var a ta
  mActions[`ta`] = reflect.TypeOf(a)
  var b tb
  mActions[`tb`] = reflect.TypeOf(b)
  var c ta
  mActions[`tc`] = reflect.TypeOf(c)
}

func MagicVarFunc(action string) interface{} {
  return reflect.Zero(mActions[action]).Interface()
}

func main() {
  t := "tb"
  v := MagicVarFunc(t) // Returns a new allocated var of type interface{}
  x := v.(tb)
  x.b = 8.3
}

答案 1 :(得分:2)

首先定义一个能够完成所需事物的函数类型:

type Producer func([]byte) interface{}

制作其中一些:

func FooProducer(raw []byte) interface{} {
    foo := new(Foo)
    ... // do something to foo
    return foo
}

func BarProducter(raw []byte) interface{} {
    bar := new(Bar)
    ... // do something to bar
    return bar
}

将它们贴在地图上:

likeThis := map[string]Producer{
    "foo": FooProducer,
    "bar": BarProducer,
}

然后只做其中一个:

myVal := likeThis[someString](raw)

但你可能想要定义一些界面并使你的制作人更像是:

type Producer func([]byte) MyAwesomeInterface

因为你可能想要对那些你正在解码的东西做一些常见的事情。您也可能想要处理错误的字符串输入,例如-a-this:

f, ok := likeThis[someString]
if !ok {
    // return, break, panic... something, just get the hell away from here.
}
myVal := f(raw)

检查类型的整个概念在Go中有点麻烦。与试图用类型系统进行反射体操相比,添加新类型的工作通常要少一些。

答案 2 :(得分:2)

jorelli的回答非常好。我只想展示一些选择。您的“真实示例”看起来与命令调度基本相似,命令参数使用JSON指定。从简单的代码开始,

package main

import (
    "encoding/json"
    "fmt"
)

func inHandler(action string, data []byte) {
    arg := make(map[string]interface{})
    json.Unmarshal(data, &arg)
    switch action {
    case "say":
        fmt.Printf("You say, %q to %s\n", arg["msg"], arg["to"])
    case "exit":
        fmt.Println("Good bye")
    }
}

func main() {
    inHandler(`say`, []byte(`{"to":"Sonia","msg":"Please help me!"}`))
    inHandler(`exit`, []byte(`{}`))
}

通过将案例添加到switch语句来注册新命令。是的,不认为你喜欢那样。所以,添加你的map和init()的想法,

package main

import (
    "encoding/json"
    "fmt"
)

type jmap map[string]interface{}

var mActions = map[string]func(jmap){}

func init() {
    mActions["say"] = func(arg jmap) {
        fmt.Printf("You say, %q to %s\n", arg["msg"], arg["to"])
    }
}

func init() {
    mActions["exit"] = func(jmap) { fmt.Println("Good bye") }
}

func inHandler(action string, data []byte) {
    args := make(jmap)
    json.Unmarshal(data, &args)
    mActions[action](args)
}

func main() {
    inHandler(`say`, []byte(`{"to":"Sonia","msg":"Please help me!"}`))
    inHandler(`exit`, []byte(`{}`))
}

现在,如果您愿意,可以将每个init函数放在一个单独的源文件中,并通过创建一个带有新init函数的新源文件来注册新命令。

程序的其余部分被简化,假设命令具有平坦的参数列表,JSON将始终将其编码为对象。这允许您为每个命令省去单独的Go结构定义。 inHandler只为所有命令创建相同类型的对象(映射),将其解组到其中,并将其传递给命令。如果你想处理一个更随意的JSON,你可以解组成一个空接口,并且这些函数必须做一些额外的工作来挖掘参数。如果那是太多的工作并且你真的想直接解组成一个结构,那么你就会接近jorelli的解决方案,让每个命令函数解组它自己的JSON。