【问题标题】:How can I check what embedded type a concrete type is composed of?如何检查具体类型由哪些嵌入类型组成?
【发布时间】:2024-01-20 10:22:01
【问题描述】:

我怀疑我试图强迫 go 以 OOP 方式行事,但我不知道 go 习惯用法来做我想做的事。

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

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

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

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

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

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

}

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

下面的代码反映了我想要发生的事情。

for {
    select {
    case m := <-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
      }
   }
}

如何用惯用的方式解决这个问题?

【问题讨论】:

    标签: go composition embedding


    【解决方案1】:

    您可以在界面中定义常用命令的东西

    type Commander interface{
        DoCommonStuff()
    }
    

    为命令结构实现它

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

    然后断言

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

    您的其他代码应该保持不变

    【讨论】:

    • 如果您知道Data 将始终是某种命令,您可以将结构中的类型更改为Commander 并消除类型断言,并获得一些类型安全来启动。
    【解决方案2】:

    一种方法是使用反射从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
    }
    
    //Call GetUserInfo then perform common operation
    user := GetUserInfo(m.Data)
    if user != nil {
        //Do auth and other common stuff
    }
    

    【讨论】:

      最近更新 更多