简体   繁体   English

父结构上的Golang嵌入式接口

[英]Golang embedded interface on parent struct

I've got a program that is trying to implement functions on "subclasses", where the parent can check to see if the interface is implemented. 我有一个试图在“子类”上实现函数的程序,其中父进程可以检查接口是否已实现。 For perspective, it's really dealing with REST URL generation based on if methods exist. 对于透视图,它实际上是基于if方法存在来处理REST URL生成。

What I'm running into is that based on the following pattern, both the IList and IGet interfaces are found on the TestController object, when only 1 is implemented. 我遇到的是基于以下模式,当实现仅1时,IList和IGet接口都可以在TestController对象上找到。 When the IGet interface is called I get a panic. 当调用IGet接口时,我感到恐慌。

I would rather not make concrete definitions of the Get/List on the base struct and then have to override them, would much rather do the test for existence then go from there. 我宁愿不在基础结构上做出Get / List的具体定义,然后必须覆盖它们,更愿意进行存在的测试,然后从那里开始。

Here's a go playground link as well https://play.golang.org/p/5j58fejeJ3 这是一个游乐场链接以及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()
}

One thing you seem to be missing is how embedding interfaces affects a structure in Go. 您似乎缺少的一件事是嵌入接口如何影响Go中的结构。 See, embedding promotes all of the methods of the embedded type (struct or interface, doesn't matter) to be methods of the parent type, but called using the embedded object as the receiver. 请参阅嵌入促进嵌入类型的所有方法(结构或接口,无关紧要)是父类型的方法,但使用嵌入对象作为接收器调用。

The practical side effect of this is that embedding an interface into a structure guarantees that that structure fulfills the interface it is embedding, because it by definition has all of the methods of that interface. 这样做的实际副作用是将接口嵌入到结构中可以保证该结构满足其嵌入的接口,因为它根据定义具有该接口的所有方法。 Trying to call any of those methods without defining something to fill that interface field in the struct, however, will panic, as that interface field defaults to nil . 然而,试图调用任何这些方法而不定义某些东西来填充结构中的那个接口字段会引起恐慌,因为该接口字段默认为nil

As a result, your type assertions will always be true. 因此,您的类型断言将始终为真。 BaseAppController embeds both the IGet and IList interfaces, and therefore always fulfills both. BaseAppController嵌入了IGetIList接口,因此始终满足这两者。

If you want duck-typing, where behavior is selectively enabled based on the presence or absence of methods on a type, you need to use something similar to how the standard library io.WriterTo interface works. 如果你想要鸭子类型,根据类型上方法的存在与否选择性地启用行为,你需要使用类似于标准库io.WriterTo接口的工作方式。 This interface, and io.ReaderFrom , are optional interfaces that io.Writer and io.Reader objects can implement to directly write to or read from another source, instead of the io package needing to buffer the read data or data to be written itself. 此接口和io.ReaderFromio.Writerio.Reader对象可以实现的可选接口,可以直接写入或读取其他源,而不是需要缓冲读取数据或要自行写入的数据的io包。

The basic gist is that you define an underlying interface with the required methods on it, and that's what you pass around. 基本要点是,您可以使用所需的方法定义底层接口,这就是您传递的内容。 You then have one or more optional interfaces, which you can check the passed type to see if they fulfill, and if so, use that optional interface's methods (and if not, revert to default behavior). 然后,您有一个或多个可选接口,您可以检查传递的类型以查看它们是否满足,如果是,则使用该可选接口的方法(如果不是,则恢复为默认行为)。 Embedding isn't needed in this case. 在这种情况下不需要嵌入。

Embedding of interfaces, rather than being for duck-typing, is more about polymorphism. 嵌入接口,而不是用于鸭子打字,更多的是多态性。 As an example, if you wanted to access a SQL database, but wanted to be able to handle both standard database calls and calls within a transaction, you could make a structure that holds the joint methods of the two types ( sql.DB and sql.Tx ) like this: 例如,如果您想访问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)
}

Then you make a structure like this: 然后你做一个像这样的结构:

type DBHandle struct {
    dber
}

Now you can store either a sql.DB or a sql.Tx in that dber slot of the structure, and anything using DBHandle (as well as all methods of DBHandle itself) can call Query() , QueryRow() , and Exec() on the DBHandle without having to know whether they are being called within the scope of a transaction or not (remember, though, that interface field must be initialized first!) 现在你可以在结构的dber槽中存储sql.DBsql.Tx ,使用DBHandle任何东西(以及DBHandle本身的所有方法)都可以调用Query()QueryRow()Exec()DBHandle上,无需知道它们是否在事务范围内被调用(但请记住,必须首先初始化该接口字段!)

This type of functionality is where embedding really starts to shine, as it allows functionality and flexibility close to a fully polymorphic inheritance system without the need of explicit "implements" or "extends" statements. 这种类型的功能是嵌入真正开始闪耀的地方,因为它允许功能和灵活性接近完全多态继承系统,而不需要显式的“implements”或“extends”语句。 It's just not really useful for the type of dynamic duck-typing behavior you're going for. 它对你想要的动态鸭子打字行为类型并没有多大用处。

Do not mix interface embedding with struct embedding . 不要将界面嵌入struct embedding混合使用。

If you embed interfaces on a struct, you are actually adding new fields to the struct with the name of the interface so if you don't init those, you will get panics because they are nil. 如果在结构上嵌入接口,实际上是使用接口名称向结构中添加新字段,因此如果不进行初始化,则会因为它们为零而引起恐慌。

Your BaseAppController expects someone to fill in IGet and IList fields with something that satifies IGet and IList interfaces respectively. 您的BaseAppController希望有人在IGetIList字段中填写分别满足IGetIList接口的内容。

This is what your BaseAppController struct really looks like: 这就是你的BaseAppController结构的真实情况:

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

It seems you are trying to do Java-style programming in Go and that doesn't end well. 看来你正在尝试在Go中进行Java风格的编程,但这并不能很好地结束。

name it with er: Getter instead of IGet 用er命名: Getter而不是IGet
you do not need to embed interface method in struct, it is enough that your struct has Get receiver method. 你不需要在struct中嵌入interface方法,你的struct有Get接收器方法就足够了。

Calling any method of a nil interface value causes a panic : 调用任何nil接口值的方法会导致恐慌
you defined ctrl.IGet but not initialized it, 你定义了ctrl.IGet但没有初始化它,
add this line inside your first if inside Init(): 如果在Init()内部,请在第一行中添加此行:

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

output is: 输出是:

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

Variables are always initialized to a well-defined value, and interfaces are no exception. 变量总是初始化为明确定义的值,接口也不例外。 The zero value for an interface has both its type and value components set to nil. 接口的零值将其类型和值组件设置为nil。
your BaseAppController lacks Get interface method. 您的BaseAppController缺少Get接口方法。

and edit last method in your sample code to this: 并将示例代码中的最后一个方法编辑为:

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

and your code runs for now. 而你的代码现在运行。

if you want to fix your current code with minimum change, just replace your main function with this (note that ctrl is pointer here): 如果你想用最小的改变来修复你当前的代码,只需用你的主函数替换它(注意ctrl是这里的指针):

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

working sample code (with minimum change): 工作示例代码(最小变化):

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()
}

also this works (embedded interface removed): 这也有效(嵌入式界面已删除):

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()
}

output: 输出:

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

If you want inherit any struct with base struct, you can write this (ugly) code, that works like a reflect package: 如果你想继承任何带有基础结构的结构,你可以编写这个(丑陋的)代码,它就像一个反射包:

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)
}

This sample is not good. 这个样本不好。 Best sample can include interface field: 最佳样本可包括界面字段:

type t1 struct{
  original i
}

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

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