I'm trying to make several types that can call the same function to perform a few common operations, without needing to duplicate them for each type. Let's call those types handlers.
The idea is that I can have a CreateHandler, a ListHandler, etc, and a function that will do default operations for those handlers, let's say, set the "Success" field to true and call the "Execute" function on the handler. I keep those examples simple to make them easy to read, but in real case there would be way more common operations.
I've tried 3 different approaches, to no avail: using a base type and embed it, using an interface implemented by types and using an empty interface as parameter.
package main
import "fmt"
type BaseHandler struct {
Success bool
}
func ( handler *BaseHandler ) Init() {
handler.Success = true
handler.Execute()
}
func ( handler *BaseHandler ) Execute() {
}
// I will have many different types doing the same kind of work,
// with their own specific fields and functions
type MyHandler struct {
BaseHandler
Message string
}
func ( handler *MyHandler ) Execute() {
fmt.Println( "I'm never called" )
}
func main() {
handler := MyHandler{}
handler.Init()
}
This won't work because the Execute
function called is the one from BaseHandler
, not from MyHandler
. Same problem if the Init
method has no receiver and takes a *BaseHandler
as parameter.
package main
import "fmt"
type BaseHandler interface {
Execute()
}
func Init( handler BaseHandler ) {
handler.Success = true
handler.Execute()
}
type MyHandler struct {
Success bool
Message string
}
func ( handler *MyHandler ) Execute() {
fmt.Println( "I'm never called" )
}
func main() {
handler := MyHandler{}
Init( handler )
}
This will fail to compile because of first line of Init
function, saying that the BaseHandler
interface does not have a Success
field.
package main
import "fmt"
func Init( handler interface{} ) {
handler.Success = true
handler.Execute()
}
type MyHandler struct {
Success bool
Message string
}
func ( handler *MyHandler ) Execute() {
fmt.Println( "I'm never called" )
}
func main() {
handler := MyHandler{}
Init( handler )
}
This will fail to compile on first line of Init
saying that interface{}
has no Success
field. I can't do type assertion there, because it would mean listing all the types which can go there, and there may be a lot.
So here is my question: how do you write shared code, that can set fields and call functions from similar types?
handler.Success = true
feels like it belongs within Execute
. Thus, you could define an interface and make Init
a function on this interface, while embedding the interface in "children":
type Handler interface {
Execute()
}
func Init(handler Handler) {
handler.Execute()
}
type BaseHandler struct {
Success bool
}
func (handler *BaseHandler) Execute() {
handler.Success = true
fmt.Println("success: true")
}
type MyHandler struct {
Handler
Message string
}
func (handler *MyHandler) Execute() {
fmt.Println("I do some work before")
handler.Handler.Execute()
fmt.Println("I do some work after")
}
func main() {
handler := &MyHandler{&BaseHandler{}, "foo"}
Init(handler)
}
Playground: https://play.golang.org/p/iDo2BQ6N5D.
Seems like you need init to be able to set success and execute something.
TL;DR Heres the playground link https://play.golang.org/p/jflmT-LpJy
So, using interfaces, I'd create the following
type SuccessableExecutable interface {
SetSuccess()
Execute()
}
func Init(se SuccessableExecutable) {
se.SetSuccess()
se.Execute()
}
Now, you'd like SetSuccess to be reusable across the many types. For this I'd create a struct which could be embedded in all those types
type Successable struct {
success bool
}
func (s *Successable) SetSuccess() {
s.success = True
}
Embedding this automatically gives you access to SetSuccess
so any struct that has the Execute
and embeds this, will also be a SuccessableExecutable
.
Putting it together, we have
package main
import (
"fmt"
)
type Successable struct {
success bool
}
func (s *Successable) SetSuccess() {
s.success = true
}
type SuccessableExecutable interface {
SetSuccess()
Execute()
}
type BaseHandler struct {
Message string
Successable
}
func (b *BaseHandler) Execute() {
fmt.Printf("%s
", b.Message);
}
func Init(se SuccessableExecutable) {
se.SetSuccess()
se.Execute()
}
func main() {
bh := &BaseHandler{ Message: "hello" }
fmt.Printf("%+v
", bh)
Init(bh)
fmt.Printf("%+v
", bh)
}
Try embedding an interface:
package main
import "fmt"
type BaseHandler struct {
Success bool
Executer // embedded, so BaseHandler inherits the method
}
type Executer interface {
Execute()
}
func (h *BaseHandler) Init() {
h.Success = true
h.Execute()
}
// I will have many different types doing the same kind of work,
// with their own specific fields and functions
type StringHandler struct {
Message string
}
func (h *StringHandler) Execute() {
fmt.Println(h.Message)
}
type IntHandler struct {
Value int
}
func (h *IntHandler) Execute() {
fmt.Println(h.Value)
}
func main() {
stringHandler := BaseHandler{Executer: &StringHandler{"Hello"}}
intHandler := BaseHandler{Executer: &IntHandler{42}}
DoInit(stringHandler)
DoInit(intHandler)
}
// These are just to prove you can pass it around as the struct
func DoInit(h BaseHandler) {
h.Init()
}
https://play.golang.org/p/aGZMnuoBh_
This lets Success be tracked within the base handler, but the actual implementation of Execute can be dynamically filled by whatever type you want (so long as it fulfills that interface).
Personally, I like to take it another step further, and pass around the interface-embedding-struct as another interface (either the same as embedded, since a structure automatically implements all interfaces that it embeds, or as another interface with other or additional methods).
Also, a related word of caution: I always recommend passing pointers into interfaces when you're doing something like this. It's an interesting quirk of Go that an interface storing a pointer can access both the pointer and value receiver methods of that stored type, but an interface storing a value directly (even though the interface will in most cases still store a pointer to the value) can only access the value receiver methods. For example: https://play.golang.org/p/6qOYhol_y_