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.