0

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.

Kostya Vasilyev
  • 852
  • 1
  • 11
  • 27
  • 1
    Possible duplicate of [Calling function that takes parent with struct embedding parent doesn't work](https://stackoverflow.com/questions/37170513/calling-function-that-takes-parent-with-struct-embedding-parent-doesnt-work) – Jonathan Hall Dec 31 '18 at 13:54
  • Thank you @Flimzy for your comments. What I'm trying to do is, to recap: Base protocol defined by interface. Implementations are in a bunch of structs / functions on those structs. And - here is the essence really - there are some "boilerplate" implementation pieces which are called through the interface and that should be able to do some work and call into methods of the enclosing ("real subclass") object. And no it's not quite like #37170513, there are no interfaces there. – Kostya Vasilyev Dec 31 '18 at 13:55
  • 2
    "it "does not know" that it's part of a larger object" -- that's exactly right. Because it's embedded, not inherited. – Jonathan Hall Dec 31 '18 at 13:56
  • 2
    I suggest rewriting your question (or asking a new one) to ask how to re-use common code across multiple implementations, rather than trying to shoe-horn OOP into Go (which is always ugly, and rarely works). – Jonathan Hall Dec 31 '18 at 13:58
  • If you have pointers on "how to re-use common code across multiple implementations" - in the context of a hierarchy of classes - I'm all ears. PS - "hierarchy of classes" doesn't exist in Go you will say of course, but what would you call Command_Login / Command_GetData / Command_SetData all defining a common Command interface, if not a hierarchy of command classes? – Kostya Vasilyev Dec 31 '18 at 14:01
  • 2
    "Interface implementations" is the proper term. – Jonathan Hall Dec 31 '18 at 14:02
  • Interface implementations is the technique, hierarchy of command object classes is the meaning. If you have pointers on "how to re-use common code across multiple implementations" - with "fine grained callbacks" possible - I'm all ears. If you do. – Kostya Vasilyev Dec 31 '18 at 14:05
  • 1
    "hierarchy of command object classes" doesn't exist in Go. So there's no term for that concept in Go. – Jonathan Hall Dec 31 '18 at 14:10
  • 2
    See answers [one](https://stackoverflow.com/questions/30622605/can-embedded-struct-method-have-knowledge-of-parent-child/30629132?r=SearchResults#30629132); and [two](https://stackoverflow.com/questions/48079775/type-composition-overriding-interface-types/48079955?r=SearchResults#48079955); and [three](https://stackoverflow.com/questions/44526957/how-can-i-organise-this-go-code-that-redefines-methods-from-an-embedded-type-to/44527419?r=SearchResults#44527419). – icza Dec 31 '18 at 14:13
  • 1
    The problem is you're coming at your implementation need from an OO perspective in a non-OO language. You need to rethink your design from the ground up - i.e., from your requirements - rather than trying to find a way to do something in Go in some way equivalent to how you would do it in an OO language. – Adrian Dec 31 '18 at 14:16
  • 2
    This is also similar to what you want to do: [How to implement an abstract class in Go?](https://stackoverflow.com/questions/30261032/how-to-implement-an-abstract-class-in-go/30261572#30261572) – icza Dec 31 '18 at 14:20
  • Well the requirements are as I wrote - the interface defines a method whose implementation is often "boilerplate" and I'd like to implement that common logic in one place and have it turn around and call finer-grained methods (defined on possibly many structs that also implement the interface). This is a great example here but there is no good answer: https://stackoverflow.com/a/1727737/2342806 – Kostya Vasilyev Dec 31 '18 at 14:21
  • Or putting differently - implementation reuse, specifically "abstract common code" that has some common parts (fixed) and some varying parts (defined differently by various structs / interface implementations). – Kostya Vasilyev Dec 31 '18 at 14:25
  • 2
    Yet another related, possible solution: [Ensuring embedded structs implement interface without introducing ambiguity](https://stackoverflow.com/questions/36710259/ensuring-embedded-structs-implement-interface-without-introducing-ambiguity/36711554#36711554) – icza Dec 31 '18 at 14:32

1 Answers1

0

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

blobdon
  • 1,176
  • 7
  • 11
  • Yes that's what I was thinking too: https://play.golang.org/p/CVZQvMfLoum Similar to your solution (onDataCalled = true should be set by onData callback however, not directly by Execute). – Kostya Vasilyev Dec 31 '18 at 14:33
  • However, this leads to code duplication: Command_Login # Execute / Command_GetData # Execute / Command_SetData # Execute / Command_xxx # Execute and so on, each with - again - boilerplate implementation of command.connection.execute(command) just to be able to preserve the "lowest" command type. – Kostya Vasilyev Dec 31 '18 at 14:49