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

huangapple go评论95阅读模式
英文:

Dynamically create variables of certain type based on string in Go

问题

如何根据字符串的值创建特定类型的变量?

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

t := "tb"
v := MagicVarFunc(t) // 返回一个新分配的类型为interface{}的变量
v.(tb).b = 8.3

在下面的示例中,我根据一个字符串动态创建变量。这是通过在一个映射中注册每个结构类型,其中字符串是键,类型的空指针是值来实现的。
每个类型都实现了一个具有New()方法的接口,该方法返回该特定类型的新变量。

下面的示例非常接近我想要做的事情,其中每个操作都有一组JSON编码的数据,这些数据将填充相应的结构。我之所以这样构造是因为我希望能够创建新的独立操作并将其注册到映射中。

我不确定我是否滥用了语言。
如果我完全疯了,有人可以给我一些建议吗?是否有明显更简单的方法?

package main

import (
	"fmt"
	"encoding/json"
)

// 我对一个操作的要求只是它可以被执行
type ActionHandler interface {
	Exec()
	New() ActionHandler
}

// 我的操作列表
var mActions = make(map[string]ActionHandler)

// 退出操作(退出程序)
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
}

// 说话操作(对某人说一句话)
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(`{}`))
}
英文:

Simple version
How can you create a variable of a certain type based upon the value of a string?

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

The true example
In my, surprisingly enough, working example below, I am dynamically creating variables based on a string. This is done by registering each struct type in a map with the string being the key and a nil-pointer of the type being the value.
Each type implements an interface with the method New() which returns a new variable of that specific type.

The example below is very close to what I wish to do, where each action has a set of JSON encoded data which will populate the corresponding struct. The way I've structured it is also because I wish to be able to create new stand alone actions that I register to the map.

I am not sure if am abusing the language now.
May anyone give me any pointers if I am completely out of my mind? Is there an obviously easier way?

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(`{}`))
}

答案1

得分: 8

你可以使用反射来获取零值,或者使用反射来分配一个新值(类似于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) // 返回一个新分配的类型为interface{}的变量
  x := v.(tb)
  x.b = 8.3
}
英文:

You can use reflection to get the zero value of, or to allocate a new value (like new) of a type using reflection, if you can get the Type value at runtime. However, I don't think there is a way to get the Type from a string. You would need to have a value of that type to get the type itself.

I adopted your idea, of using a map. I map the string to the type itself, which you can get using reflect.TypeOf, which gets the type out of an interface value. Then I used reflect.Zero to get the zero value of that type (a convenient value that exists for every type). Then I got the value out as an interface.

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
}

答案2

得分: 3

开始定义一个函数类型,该函数类型执行你想要的操作:

type Producer func([]byte) interface{}

创建几个这样的函数:

func FooProducer(raw []byte) interface{} {
    foo := new(Foo)
    ... // 对foo进行一些操作
    return foo
}

func BarProducter(raw []byte) interface{} {
    bar := new(Bar)
    ... // 对bar进行一些操作
    return bar
}

将它们放入一个map中:

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

然后只需执行以下操作之一:

myVal := likeThis[someString](raw)

但你可能想要定义一些接口,并将你的生产者函数更改为类似这样的形式:

type Producer func([]byte) MyAwesomeInterface

因为你可能想要对解码的这些对象进行一些共同的操作。你可能还想处理输入字符串错误的情况,像这样:

f, ok := likeThis[someString]
if !ok {
    // 返回、中断、抛出异常...做些什么,只要离开这里就行。
}
myVal := f(raw)

在Go语言中,检查类型的整个概念有点繁琐。通常,添加新类型比尝试在类型系统中进行反射操作更容易。

英文:

start by defining a function type that does the thing you want:

type Producer func([]byte) interface{}

make a few of them:

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
}

stick them in a map:

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

and then just do one of these:

myVal := likeThis[someString](raw)

but you probably want to define some interface and make your producer something more like:

type Producer func([]byte) MyAwesomeInterface

since there's probably some common stuff you want to do with those things you're decoding. You also probably want to handle the case of a bad string input, like-a-this:

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

The whole notion of inspecting types is kinda cumbersome in Go. It's generally less work to just add new types than it is to try to do reflection gymnastics with the type system.

答案3

得分: 3

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语句添加case来注册新命令。是的,我知道你不会喜欢这样。所以,添加你的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只为所有命令创建相同类型的对象(一个map),对其进行解组,并将其传递给命令。如果你想处理更多任意的JSON,你可以解组到一个空接口中,函数将需要做一些额外的工作来提取参数。如果这太麻烦,而你真的想直接解组到一个结构体中,那么你就接近jorelli的解决方案,即使每个命令函数解组自己的JSON。

英文:

jorelli's answer is very good. I'm just going to show a few options. Your "true example" looks essentially like command dispatch, with command parameters specified with JSON. To start with simple code that does this,

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(`{}`))
}

Your register new commands by adding cases to the switch statement. Yeah, didn't think you'd like that. So, adding your map and init() idea,

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(`{}`))
}

Now if you wanted, you could put each of those init functions in a separate source file and new commands could be registered by creating a new source file with a new init function.

The rest of the program is simplified with some assumptions that the commands have flat argument lists that the JSON will always encode as an object. This allows you to dispense with separate Go struct definitions for each command. inHandler just creates the same type of object (a map) for all commands, unmarshals into it, and passes it to the command. If you wanted to handle a little more arbitrary JSON, you could unmarshal into an empty interface, and the functions would have to do some extra work to dig out the arguments. If that was too much work and you really wanted to unmarshal directly into a struct, then you arrive near jorelli's solution of making each command function unmarshal its own JSON.

huangapple
  • 本文由 发表于 2012年6月21日 04:32:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/11127723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定