实际遗失电话

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
}

https://play.golang.org/p/HvaKHZWIO5W