简体   繁体   中英

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.

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. When the IGet interface is called I get a panic.

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.

Here's a go playground link as well 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. 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 .

As a result, your type assertions will always be true. BaseAppController embeds both the IGet and IList interfaces, and therefore always fulfills both.

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. 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.

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:

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

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. 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 .

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.

This is what your BaseAppController struct really looks like:

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.

name it with er: Getter instead of IGet
you do not need to embed interface method in struct, it is enough that your struct has Get receiver method.

Calling any method of a nil interface value causes a panic :
you defined ctrl.IGet but not initialized it,
add this line inside your first if inside 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.
your BaseAppController lacks Get interface method.

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

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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