douxi2670 2018-12-31 13:24
浏览 41
已采纳

实际遗失电话

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.

  • 写回答

1条回答 默认 最新

  • douying9296 2018-12-31 14:28
    关注

    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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 逻辑谓词和消解原理的运用
  • ¥15 请求分析基于spring boot+vue的前后端分离的项目
  • ¥15 三菱伺服电机按启动按钮有使能但不动作
  • ¥15 js,页面2返回页面1时定位进入的设备
  • ¥200 关于#c++#的问题,请各位专家解答!网站的邀请码
  • ¥50 导入文件到网吧的电脑并且在重启之后不会被恢复
  • ¥15 (希望可以解决问题)ma和mb文件无法正常打开,打开后是空白,但是有正常内存占用,但可以在打开Maya应用程序后打开场景ma和mb格式。
  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?