Another question on polymorphism in Go, references: Embedding instead of inheritance in Go, https://medium.com/@adrianwit/abstract-class-reinvented-with-go-4a7326525034
Motivation: there is an interface (with some methods for dealing with "the outside world") and a bunch of implementation structs of that interface.
There is a "standard" implementation of some of these methods, where common logic should be put in one place with delegation to (new) methods in the structs-implementing-the-interface ("subclasses" is not a word).
I've read the medium link above and wrote some test code. Alas, it does not work the way I expect, the actual type of a struct is lost when the call on the interface is indirect.
In C++ this is called "based class slicing" and happens when passing a polymorphic class by value. In my Go test code I'm careful to pass by reference, and then Go is not C++ (or Java).
Code: https://play.golang.org/p/lxAmw8v_kiW
Inline:
package main
import (
"log"
"reflect"
"strings"
)
// Command - interface
type Command interface {
Execute()
getCommandString() string
onData(data string)
}
// Command - implementation
type Command_Impl struct {
commandString string
conn Connection
}
func newCommand_Impl(conn Connection, data string, args ...string) Command_Impl {
var buf strings.Builder
buf.WriteString(data)
for _, key := range args {
buf.WriteString(" ")
buf.WriteString(key)
}
return Command_Impl {
conn: conn,
commandString: buf.String(),
}
}
func (self *Command_Impl) Execute() {
log.Printf("Command Impl Execute: %s", reflect.TypeOf(self))
self.conn.execute(self)
}
func (self *Command_Impl) getCommandString() string {
return self.commandString
}
func (self *Command_Impl) onData(data string) {
log.Printf("Command Impl onData: %s", data)
}
// Command - subclass
type Command_Login struct {
Command_Impl
onDataCalled bool
}
func newCommand_Login(conn Connection) *Command_Login {
return &Command_Login{
Command_Impl: newCommand_Impl(conn, "LOGIN", "user@foo.com", "pa$$w0rd"),
}
}
func (self *Command_Login) onData(data string) {
log.Printf("Command Login onData: %s", data)
self.onDataCalled = true
}
// Connection - interface
type Connection interface {
execute(command Command)
}
// Connection - impelementation
type Connection_Impl struct {
}
func newConnection_Impl() *Connection_Impl {
return &Connection_Impl{}
}
func (self *Connection_Impl) execute(command Command) {
log.Printf("Connection execute: %s, %s", command.getCommandString(), reflect.TypeOf(command))
command.onData("some data")
}
func main() {
conn := newConnection_Impl()
command := newCommand_Login(conn)
// I expect command.Execute to preserve actual type of command all the way through
// command.conn.execute(self) and then the callback onData from connection to command
// to use the onData in Command_Login
//
// This does not happen however, the test fails
command.Execute()
// This does preserve actual type of command, but isn't how I'd like to connect
// commands and connections...
//
//conn.execute(command)
if command.onDataCalled {
log.Printf("*** GOOD: Command_Login onData ***was*** called")
} else {
log.Printf("*** ERROR: Command_Login onData ***not*** called")
}
}
There is a Command interface which defines some methods.
There is a Command_Impl struct where I'd like to implement some common code that would further delegate to finer-grained methods in more structs that implement the same interface ("subclass is not a word"), similar to:
https://stackoverflow.com/a/1727737/2342806
The question:
Calling command.Execute()
which in turn calls conn.execute(self)
ends up "slicing" the Command_Login
object and inside Connection.execute
it's turned into Command_Impl
. As a result, onData
interface method defined for Command_Login
do not get called.
If I call conn.execute(command)
then the right onData
does get called, but this is not how I'd like to connect my objects (e.g. Command
already has a Connection
, but basically what I wrote above about reusing implementation).
In Go terms, I'm trying to come up with a way to delegate implementation by embedding, and have a way to for the delegate to call back into the enclosing type (which fine-tunes the delegate's logic).
Alas, it seems to not be supported by Go (at least I can't find a way) - once you delegate to an embedded struct, your calls stay entirely there in the embedded struct, it "does not know" that it's part of a larger object which may be wanting to override some of the embedded struct's methods.
What about delegating implementation by implementing the Execute interface at the shallowest depth you need?
func (self *Command_Login) Execute() {
self.Command_Impl.Execute()
log.Printf("Command Login Execute: %s", reflect.TypeOf(self))
self.onDataCalled = true
}