How can I check what embedded type a concrete type is composed of?

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

How can I check what embedded type a concrete type is composed of?

问题

我怀疑我正在试图让Go以面向对象的方式运行,但我不知道如何用Go的习惯用法来实现我想要的功能。

我有一个Message结构体,用于在客户端-服务器应用程序中传递数据:

type Message struct {
    ID   string      `json:"id,omitempty"`
    Type string      `json:"type"`
    Data interface{} `json:"data"`
}

这里的Data可以是不同的东西,例如一些命令:

type Command struct {
    User *types.UserInfo `json:"user"`
}

type CommandA struct {
    Command
    A *AData `json:"a_data"`
}

type CommandB struct {
    Command
    B *BData `json:"b_data"`
}

我想要做的是检查消息的数据类型是否为Command,并在一个地方执行所有命令共有的操作,例如授权,而不必断言命令的类型,调用适当的处理函数,然后进行授权,因为这将导致大量的代码重复。

下面的代码反映了我想要发生的情况:

for {
    select {
    case m := <-in:
        // 我想要的操作,显然不起作用,因为m.Data不是Command类型,而是实际的命令类型
        if c, ok := m.Data.(msg.Command); ok {
            // 进行授权和其他共有的操作
        }
        switch t := m.Data.(type) {
        case *msg.CommandA:
            go srv.handleCommandA(m.ID, t)
        case *msg.CommandB:
            go srv.handleCommandB(m.ID, t)
        // 等等
        default:
            // 做一些其他操作
        }
    }
}

我如何以Go的习惯用法解决这个问题?

英文:

I suspect I am trying to shoehorn go into behaving in an OOP way, but I don't know the go idiom to do what I want.

I have a Message struct that I use to pass data around in a client-server application:

type Message struct {
	ID   string      `json:&quot;id,omitempty&quot;`
	Type string      `json:&quot;type&quot;`
	Data interface{} `json:&quot;data&quot;`
}

the Data here can be different things, for example a number of Commands:

type Command struct {
	User *types.UserInfo `json:&quot;user&quot;`
}

type CommandA struct {
	Command
	A *AData `json:&quot;a_data&quot;`
}

type CommandB struct {
	CommandB
	B *BData `json:&quot;b_data&quot;`

}

What I want to do is to check that the message data type is a Command and perform actions that are common to all commands, for example authorisation, all in one place and not having to type assert what type of command, calling the appropriate handler function and then do the auth, as this would result in massive code duplication.

The code below reflects what I would like to happen.

for {
    select {
    case m := &lt;-in:
      // what I would like to do, obviously not working as
      // m.Data is not of type Command but the actual command type
      if c, ok := m.Data.(msg.Command); ok {
        // do auth and other common stuff
      }
      switch t := m.Data.(type) {
      case *msg.CommandA:
        go srv.handleCommandA(m.ID, t)
      case *msg.CommandB:
        go srv.handleCommandB(m.ID, t)
      // etc etc
      default:
        // do something
      }
   }
}

How do I solve this go idiomatically?

答案1

得分: 3

你可以在接口中定义常见的命令内容:

type Commander interface {
    DoCommonStuff()
}

为Command结构体实现该接口:

func (c Command) DoCommonStuff() {
    // 做一些操作
}

然后进行断言:

if c, ok := m.Data.(Commander); ok {
    c.DoCommonStuff()
}

你的其他代码应该不需要改动。

英文:

You can define common command stuff in interface

type Commander interface{
	DoCommonStuff()
}

implement it for Command struct

func (c Command) DoCommonStuff(){
//do stuff
}

and then assert

if c, ok := m.Data.(Commander); ok {
    c.DoCommonStuff()
}

your other code should work unchanged

答案2

得分: 0

一种方法是使用反射从Data中提取常见的字段值。在你的例子中,由于所有的Command都有User字段,我们可以使用它来判断Message.Data是否是一个命令。如果Command没有嵌入到数据中,就返回nil。示例代码:

func GetUserInfo(v interface{}) *types.UserInfo {
    vt := reflect.ValueOf(v)
    if vt.Kind() == reflect.Ptr {
        vt = vt.Elem()
    }
    if vt.Kind() != reflect.Struct {
        return nil
    }

    u := vt.FieldByName("User")
    if !u.IsValid() {
        return nil
    }

    user, ok := u.Interface().(*types.UserInfo)
    if !ok {
        return nil
    }

    return user
}

//调用GetUserInfo然后执行常见操作
user := GetUserInfo(m.Data)
if user != nil {
    //进行身份验证和其他常见操作
}

以上是翻译好的内容,请确认是否满意。

英文:

One approach is using reflection to extract common field value from the Data. In your example, since all Command has User field, we can use it to identify whether Message.Data is a command or not. If Command is not embedded to the data, simply return nil. Example code:

func GetUserInfo(v interface{}) *types.UserInfo {
    vt := reflect.ValueOf(v)
    if vt.Kind() == reflect.Ptr {
        vt = vt.Elem()
    }
    if vt.Kind() != reflect.Struct {
        return nil
    }

    u := vt.FieldByName(&quot;User&quot;)
    if !u.IsValid() {
        return nil
    }

    user, ok := u.Interface().(*types.UserInfo)
    if !ok {
        return nil
    }

    return user
}

//Call GetUserInfo then perform common operation
user := GetUserInfo(m.Data)
if user != nil {
    //Do auth and other common stuff
}

huangapple
  • 本文由 发表于 2017年5月11日 17:56:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/43912149.html
匿名

发表评论

匿名网友

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

确定