繁体   English   中英

父结构上的Golang嵌入式接口

[英]Golang embedded interface on parent struct

我有一个试图在“子类”上实现函数的程序,其中父进程可以检查接口是否已实现。 对于透视图,它实际上是基于if方法存在来处理REST URL生成。

我遇到的是基于以下模式,当实现仅1时,IList和IGet接口都可以在TestController对象上找到。 当调用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中的结构。 请参阅嵌入促进嵌入类型的所有方法(结构或接口,无关紧要)是父类型的方法,但使用嵌入对象作为接收器调用。

这样做的实际副作用是将接口嵌入到结构中可以保证该结构满足其嵌入的接口,因为它根据定义具有该接口的所有方法。 然而,试图调用任何这些方法而不定义某些东西来填充结构中的那个接口字段会引起恐慌,因为该接口字段默认为nil

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

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

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

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

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上,无需知道它们是否在事务范围内被调用(但请记住,必须首先初始化该接口字段!)

这种类型的功能是嵌入真正开始闪耀的地方,因为它允许功能和灵活性接近完全多态继承系统,而不需要显式的“implements”或“extends”语句。 它对你想要的动态鸭子打字行为类型并没有多大用处。

不要将界面嵌入struct embedding混合使用。

如果在结构上嵌入接口,实际上是使用接口名称向结构中添加新字段,因此如果不进行初始化,则会因为它们为零而引起恐慌。

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

这就是你的BaseAppController结构的真实情况:

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

看来你正在尝试在Go中进行Java风格的编程,但这并不能很好地结束。

用er命名: Getter而不是IGet
你不需要在struct中嵌入interface方法,你的struct有Get接收器方法就足够了。

调用任何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)
}

而你的代码现在运行。

如果你想用最小的改变来修复你当前的代码,只需用你的主函数替换它(注意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

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

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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM