【问题标题】:Explanation of checking if value implements interface检查值是否实现接口的说明
【发布时间】:2015-03-04 09:57:23
【问题描述】:

我已经阅读了“Effective Go”和其他类似的问答:golang interface compliance compile type check,但我仍然无法正确理解如何使用这种技术。

请看示例:

type Somether interface {
    Method() bool
}

type MyType string

func (mt MyType) Method2() bool {
    return true
}

func main() {
    val := MyType("hello")

    //here I want to get bool if my value implements Somether
    _, ok := val.(Somether)
    //but val must be interface, hm..what if I want explicit type?

    //yes, here is another method:
    var _ Iface = (*MyType)(nil)
    //but it throws compile error
    //it would be great if someone explain the notation above, looks weird
}

如果实现了接口,有没有简单的方法(例如不使用反射)检查值?

【问题讨论】:

  • 怎么样 _, ok := interface{}(val).(Somether) ?

标签: interface go type-conversion


【解决方案1】:

我有一个解决方案可以用来补充恐慌处理程序模式
我没有对代码进行详尽的测试,但随意测试是肯定的
我欢迎任何改进建议或其他 Go 专家的东西

// -------------------------------------------------------------- //
// hasErrIface -
// ---------------------------------------------------------------//
func hasErrIface(v reflect.Value) (error, bool) {
    // CanInterface reports whether Interface can be used without panicking
    if !v.CanInterface() {
        return nil, false
    }
    // Interface panics if the Value was obtained by accessing unexported struct fields
    err, ok := v.Interface().(error)
    return err, ok
}

// -------------------------------------------------------------- //
// HasErrKind
// ---------------------------------------------------------------//
func HasErrKind(r interface{}) (err error, isErr bool) {
    err = nil
    isErr = false
    v := reflect.ValueOf(r)
    switch v.Kind() {
    case reflect.Struct:
        errtype := reflect.TypeOf((*error)(nil)).Elem()
        if v.Type().Implements(errtype) {
            err, isErr = v.Interface().(error)
        }
    case reflect.Ptr:
        err, isErr = hasErrIface(v)
    case reflect.Interface:
        err, isErr = hasErrIface(v)
    }
    return
}

// -------------------------------------------------------------- //
// EvalErrKind
// ---------------------------------------------------------------//
func EvalErrKind(r interface{}) (errval error) {
    err, isErr := HasErrKind(r)
    if !isErr {
        errtxt := "Unknown system error - %v :\n%v"
        v := reflect.ValueOf(r)
        return fmt.Errorf(errtxt, v.Type(), v)
    }
    return err
}

// -------------------------------------------------------------- //
// PanicHandler
// ---------------------------------------------------------------//
func PanicHandler(errHandler func(error)) func() {
    return func() {
        var err error
        if r := recover(); r != nil {
            switch r.(type) {
            case ApiError:
                err = r.(ApiError)
            default:
                err = EvalErrKind(r)
            }
            if errHandler != nil {
                errHandler(err)
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    你也可以采用Alpha的解决方案:

    val := MyType("hello")
    var i interface{} = val
    v, ok := i.(Somether)
    

    ...并进一步减少它:

    val := MyType("hello")
    v, ok := interface{}(val).(Somether)
    

    如果您尝试测试一次性方法,您甚至可以执行以下操作:

    val := MyType("hello")
    v, ok := interface{}(val).(interface {
        Method() bool
    }) 
    

    注意:请确保您对“指针接收器”与“值接收器”实现非常小心。如果实现使用指针,则在传入值对象时断言将失败。如果实现使用值接收器,则两个断言都会通过。

    // Implement Somether as a POINTER receiver method
    func (mt *MyType) Method() bool {
      return true
    }
    
    func main() {
        val := MyType("hello")
    
        v, ok := interface{}(val).(Somether)
        fmt.Println(v, ok)
        // Output:  <nil> false
    
        // Notice the pass by reference
        v, ok := interface{}(&val).(Somether)
        fmt.Println(v, ok)
        // Output:  0xc000010200 true
    
    }
    

    // Implement Somether as a VALUE receiver method
    func (mt MyType) Method() bool {
      return true
    }
    
    func main() {
        val := MyType("hello")
    
        v, ok := interface{}(val).(Somether)
        fmt.Println(v, ok)
        // Output:  hello true
    
        // Notice the pass by reference
        v, ok := interface{}(&val).(Somether)
        fmt.Println(v, ok)
        // Output:  0xc00008e1e0 true
    
    }
    

    【讨论】:

      【解决方案3】:

      也可以通过以下方式使用reflect.TypeImplements(u Type) bool方法:

      package main
      
      import (
          "reflect"
      )
      
      type Somether interface {
          Method() bool
      }
      
      type MyType string
      
      func (mt MyType) Method() bool {
          return true
      }
      
      func main() {
      
          inter := reflect.TypeOf((*Somether)(nil)).Elem()
      
          if reflect.TypeOf(MyType("")).Implements(inter) {
              print("implements")
          } else {
              print("doesn't")
          }
      }
      

      您可以在documentation 中阅读更多信息。

      【讨论】:

      • 这是一个规范的例子,很好地记录在文档中。但是,@Alpha 解决方案要漂亮得多
      【解决方案4】:

      以下将起作用:

      val:=MyType("hello")
      var i interface{}=val
      v, ok:=i.(Somether)
      

      【讨论】:

        【解决方案5】:

        如果你不知道值的类型,你只需要检查一个值是否实现了一个接口。 如果类型已知,则编译器会自动进行检查。

        如果你真的想检查,你可以用你给的第二种方法来做:

        var _ Somether = (*MyType)(nil)
        

        编译时会出错:

        prog.go:23: cannot use (*MyType)(nil) (type *MyType) as type Somether in assignment:
            *MyType does not implement Somether (missing Method method)
         [process exited with non-zero status]
        

        您在这里所做的是将MyType 类型的指针(和nil 值)分配给Somether 类型的变量,但由于变量名称为_,因此它被忽略了。

        如果MyType 实现了Somether,它将编译并且什么都不做

        【讨论】:

        • 由于右手有一个指向MyType指针,为什么黑色标识符不一定必须是*Somether?我还在学习。 :-)
        • 你可以把接口值想象成一个容器,你可以在里面放任何你想要的东西,只要它实现了正确的方法。它可以包含指向结构的指针,也可以直接包含结构。根据经验,您永远不需要创建指向接口值的指针
        猜你喜欢
        • 2021-09-30
        • 2010-09-21
        • 2016-09-29
        • 1970-01-01
        • 1970-01-01
        • 2018-10-19
        • 2012-03-06
        • 1970-01-01
        • 2021-12-01
        相关资源
        最近更新 更多