【问题标题】:Golang embedded interface on parent struct父结构上的 Golang 嵌入式接口
【发布时间】:2016-06-26 22:11:33
【问题描述】:

我有一个程序试图在“子类”上实现功能,父类可以在其中检查接口是否实现。从角度来看,它实际上是根据是否存在方法来处理 REST URL 生成。

我遇到的是基于以下模式,IList 和 IGet 接口都在 TestController 对象上找到,而只实现了 1。当调用 IGet 接口时,我会感到恐慌。

我宁愿不在基础结构上对 Get/List 进行具体定义,然后必须覆盖它们,而是宁愿进行存在性测试然后从那里开始。

这里还有一个去游乐场链接https://play.golang.org/p/5j58fejeJ3

package main

import "fmt"

type IGet interface {
    Get(int)
}

type IList interface {
    List(int)
}

type Application struct {
    name    string
}

type BaseAppController struct {
    *Application

    IGet
    IList
}

type TestController struct {
    *BaseAppController
}

func (ctrl *BaseAppController) Init() {
    fmt.Println("In Init")

    if f, ok := interface{}(ctrl).(IGet); ok {
        fmt.Println("Controller Found GET", f)
    } else {
        fmt.Println("Controller NOT Found GET", f)
    }

    if f, ok := interface{}(ctrl).(IList); ok {
        fmt.Println("Controller Found LIST", f)
    } else {
        fmt.Println("Controller NOT Found LIST", f)
    }
}

func (ctrl *BaseAppController) Call() {
    fmt.Println("In Call")

    if f, ok := interface{}(ctrl).(IGet); ok {
        fmt.Println("Controller Found GET - going to call", f)

        f.Get(7)
    } else {
        fmt.Println("Controller NOT Found GET - can't call", f)
    }
}

// Test controller implements the Get Method
func (ctrl *TestController) Get(v int) {
    fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
}

func main() {
    app := Application{"hithere"}
    ctrl := TestController{&BaseAppController{Application: &app}}

    ctrl.Init()

    ctrl.Call()
}

【问题讨论】:

    标签: go


    【解决方案1】:

    您似乎缺少的一件事是嵌入接口如何影响 Go 中的结构。看,embedding 将嵌入类型的所有方法(结构或接口,无关紧要)提升为父类型的方法,但使用嵌入对象作为接收者来调用。

    这样做的实际副作用是,将接口嵌入到结构中可以保证该结构满足它所嵌入的接口,因为根据定义,它具有该接口的所有方法。但是,如果尝试调用任何这些方法而不定义填充结构中的接口字段的内容,则会出现恐慌,因为该接口字段默认为nil

    因此,您的类型断言将始终为真。 BaseAppController 嵌入了 IGetIList 接口,因此始终满足两者。

    如果你想要鸭子类型,根据类型上是否存在方法来选择性地启用行为,你需要使用类似于标准库io.WriterTo interface 工作方式的东西。此接口和io.ReaderFrom 是可选接口,io.Writerio.Reader 对象可以实现直接写入或读取另一个源,而不是io 包需要缓冲读取数据或要写入的数据自己。

    基本要点是您定义一个带有 必需 方法的底层接口,这就是您传递的内容。然后,您有一个或多个可选接口,您可以检查传递的类型以查看它们是否满足,如果满足,则使用该可选接口的方法(如果没有,则恢复为默认行为)。在这种情况下不需要嵌入。

    接口的嵌入,而不是用于鸭子类型,更多的是关于多态性。例如,如果您想访问 SQL 数据库,但希望能够同时处理标准数据库调用和事务中的调用,则可以创建一个包含两种类型的联合方法的结构(sql.DB 和 @ 987654332@) 像这样:

    type dber interface {
        Query(query string, args ...interface{}) (*sql.Rows, error)
        QueryRow(query string, args ...interface{}) *sql.Row
        Exec(query string, args ...interface{}) (sql.Result, error)
    }
    

    然后你做一个这样的结构:

    type DBHandle struct {
        dber
    }
    

    现在您可以在结构的dber 槽中存储sql.DBsql.Tx,任何使用DBHandle(以及DBHandle 本身的所有方法)都可以调用Query()QueryRow()Exec()DBHandle 上,而不必知道它们是否在事务范围内被调用(但请记住,必须首先初始化该接口字段!)

    这种类型的功能是嵌入真正开始大放异彩的地方,因为它允许功能和灵活性接近于完全多态的继承系统,而无需显式的“实现”或“扩展”语句。对于您想要的动态鸭子打字行为类型来说,它并不是真的有用。

    【讨论】:

      【解决方案2】:

      不要将interface embeddingstruct embedding混用。

      如果你在结构上嵌入接口,你实际上是在用接口名称向结构中添加新字段,所以如果你不初始化这些,你会因为它们是 nil 而感到恐慌。

      您的BaseAppController 期望有人在IGetIList 字段中分别填写满足IGetIList 接口的内容。

      这就是你的 BaseAppController 结构的真正样子:

      type BaseAppController struct {
          Application *Application
          IGet        IGet
          IList       IList
      }
      

      您似乎正在尝试在 Go 中进行 Java 风格的编程,但结果并不好。

      【讨论】:

      • 如果您是“X”并实现“Y”,则这是更多 Python 风格的代码(鸭子类型),然后添加以下行为。虽然我意识到这是不可能的。
      • 不管是结构还是接口。在这两种情况下,您都将另一个字段添加到您的结构中,该字段将与嵌入类型具有相同的名称。它会恐慌,因为在 Go 接口内部只有两个指针——指向类型信息的指针和指向数据的指针。如果接口类型字段为 nil,那么两个指针都为 nil,当你试图用它做某事时你会感到恐慌。如果您嵌入一个指向结构的指针,同样的事情 - 支持字段将默认为 nil,当您尝试访问它时会感到恐慌。
      【解决方案3】:

      用 er 命名它:Getter 而不是 IGet
      您不需要在结构中嵌入接口方法,您的结构具有获取接收器方法就足够了。

      调用 nil 接口值的任何方法都会导致恐慌
      你定义了ctrl.IGet 但没有初始化它,
      如果在 Init() 中添加这一行:

      fmt.Printf("f=%T IGet:T=%T V=%[2]v\n", f, ctrl.IGet)
      

      输出是:

      f=*main.BaseAppController IGet:T=<nil> V=<nil>
      

      变量总是被初始化为一个明确定义的值,接口也不例外。接口的零值将其类型和值组件都设置为 nil。
      您的 BaseAppController 缺少 Get 接口方法。

      并将示例代码中的最后一个方法编辑为:

      func (ctrl *BaseAppController) Get(v int) {
          fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
      }
      

      您的代码现在可以运行。

      如果你想用最少的改动来修复你当前的代码,只需用这个替换你的 main 函数(注意这里的 ctrl 是指针):

      func main() {
          app := Application{"hithere"}
          ctrl := &TestController{&BaseAppController{Application: &app}}
          ctrl.IGet = interface{}(ctrl).(IGet)
          ctrl.Init()
          ctrl.Call()
      }
      

      工作示例代码(更改最少):

      package main
      
      import "fmt"
      
      type IGet interface {
          Get(int)
      }
      
      type IList interface {
          List(int)
      }
      
      type Application struct {
          name string
      }
      
      type BaseAppController struct {
          *Application
      
          IGet
          IList
      }
      
      type TestController struct {
          *BaseAppController
      }
      
      func (ctrl *BaseAppController) Init() {
          fmt.Println("In Init")
      
          if f, ok := interface{}(ctrl).(IGet); ok {
              fmt.Println("Controller Found GET", f)
          } else {
              fmt.Println("Controller NOT Found GET", f)
          }
      
          if f, ok := interface{}(ctrl).(IList); ok {
              fmt.Println("Controller Found LIST", f)
          } else {
              fmt.Println("Controller NOT Found LIST", f)
          }
      }
      
      func (ctrl *BaseAppController) Call() {
          fmt.Println("In Call")
      
          if f, ok := interface{}(ctrl).(IGet); ok {
              fmt.Println("Controller Found GET - going to call", f)
      
              f.Get(7)
          } else {
              fmt.Println("Controller NOT Found GET - can't call", f)
          }
      }
      
      // Test controller implements the Get Method
      func (ctrl *TestController) Get(v int) {
          fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
      }
      
      func main() {
          app := Application{"hithere"}
          ctrl := &TestController{&BaseAppController{Application: &app}}
          ctrl.IGet = interface{}(ctrl).(IGet)
          ctrl.Init()
          ctrl.Call()
      }
      

      这也有效(移除了嵌入式接口):

      package main
      
      import "fmt"
      
      type IGet interface {
          Get(int)
      }
      
      type IList interface {
          List(int)
      }
      
      type Application struct {
          name string
      }
      
      type BaseAppController struct {
          *Application
      }
      
      type TestController struct {
          *BaseAppController
      }
      
      func (ctrl *TestController) Init() {
          fmt.Println("In Init")
      
          if f, ok := interface{}(ctrl).(IGet); ok {
              fmt.Println("Controller Found GET", f)
          } else {
              fmt.Println("Controller NOT Found GET", f)
          }
      
          if f, ok := interface{}(ctrl).(IList); ok {
              fmt.Println("Controller Found LIST", f)
          } else {
              fmt.Println("Controller NOT Found LIST", f)
          }
      }
      
      func (ctrl *TestController) Call() {
          fmt.Println("In Call")
      
          if f, ok := interface{}(ctrl).(IGet); ok {
              fmt.Println("Controller Found GET - going to call", f)
      
              f.Get(7)
          } else {
              fmt.Println("Controller NOT Found GET - can't call", f)
          }
      }
      
      // Test controller implements the Get Method
      func (ctrl *TestController) Get(v int) {
          fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v)
      }
      
      func main() {
          app := Application{"hithere"}
          ctrl := TestController{&BaseAppController{Application: &app}}
      
          ctrl.Init()
      
          ctrl.Call()
      }
      

      输出:

      In Init
      Controller Found GET &{0xc082026028}
      Controller NOT Found LIST <nil>
      In Call
      Controller Found GET - going to call &{0xc082026028}
      Hi name=hithere v=7
      

      【讨论】:

      • 实际的代码从来没有 IGet(只是为了示例目的而懒惰)。您发布的第二个版本将 Init 置于最终结构上,这当然使其可以访问接口。如果我的目标是拥有有效和抽象的基类,而我不必在每个控制器上都有具体的实现,那是不可能的。它可能不会去,但它会非常有用。仅供参考 - 这主要是我试图了解接口可以做什么的可能性。
      • @koblas 完全有可能,你只是在倒退。不是嵌入基类可以不必实现的接口(因为根据定义嵌入它们使其实现它们),而是将基类嵌入到附加的结构也实现了IGetIList,并具有传递所需方法的接口。示例:play.golang.org/p/SXuGc8fAMY
      【解决方案4】:

      如果你想继承任何带有基本结构的结构,你可以编写这个(丑陋的)代码,它就像一个反射包:

      package main
      
      import (
      "fmt"
      "unsafe"
      )
      
      type i interface{
        i() interface{}
        ct(i)
      }
      
      type t1 struct{
        rptr unsafe.Pointer
      }
      
      func(x *t1) i() interface{} {
        // parent struct can view child changed value, stored in rptr, as original value type with changes after store, instead of i interface
        rv:= *(*i)(x.rptr)
        fmt.Printf("%#v %d\n", rv, rv.(*t2).a)
        return rv
      }
      
      func(x *t1) ct(vv i){
        // store pointer to child value of i interface type
        // we can store any of types, i is for the sample
        x.rptr = unsafe.Pointer(&vv)
      }
      
      type t2 struct{
        t1
        a int
      }
      
      
      func main() {
        t:=&t2{}
        t.ct(t) // store original
        t.a = 123 // change original
        ti:=(t.i()).(*t2) // t.i() is a method of parent (embedded) struct, that return stored value as original with changes in interface{}
        fmt.Printf("%#v %d\n",ti, ti.a)
      }
      

      这个样本不好。 最好的样本可以包含接口字段:

      type t1 struct{
        original i
      }
      

      【讨论】:

        猜你喜欢
        • 2015-05-16
        • 2023-03-04
        • 2016-10-15
        • 2017-12-24
        • 2014-08-11
        • 2022-11-22
        • 2018-10-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多