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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With