go - 在 Go 中正确获取多态代码

标签 go

我已经多次重构我的树包,但没有找到我满意的解决方案,所以我想要一些关于最佳继续进行的建议。

我试图将问题简化为它的本质,并制作了一个由节点组成的树的简单示例。所有节点都有一组通用功能(在示例中表示为打开/关闭状态)。此外,还有几种类型的节点,每一种都有专门的行为(在示例中表示为实现 EditorInterface 并具有可见/隐藏状态的可编辑节点)。

在我的示例中,我们尝试满足所需的行为 - 可以打开任何节点,如果它是可编辑的,则在打开时应该使编辑器可见。

我的示例定义了两种类型的节点,文件夹和文档。文档是可编辑的。

我的直觉是为节点定义一个结构,并将通用功能包括为成员和方法。然后为文件夹和文档定义结构,在每个结构中嵌入一个匿名节点结构。

但是,这会导致一个问题,我的第一个示例将突出显示该问题。我创建了一个失败的简单测试:

示例 1:https://play.golang.org/p/V6UT19zVVU

// In this example the test fails because we're unable to access the interface in SetNodeState.
package main

import "testing"

func TestTree(t *testing.T) {
    n := getTestNode()
    n.SetNodeState(true)
    if !n.(*document).visible {
        t.Error("document is not visible")
    }
}

func getTestNode() NodeInterface {
    doc := &document{node: &node{}, content: "foo"}
    folder := &folder{node: &node{children: []NodeInterface{doc}}, color: 123}
    return folder.children[0]
}

type NodeInterface interface {
    SetNodeState(state bool)
}

type EditorInterface interface {
    SetEditState(state bool)
}

type node struct {
    open     bool
    parent   NodeInterface
    children []NodeInterface
}

func (n *node) SetNodeState(state bool) {

    n.open = state

    // TODO: obviously this isn't possible.
    //if e, ok := n.(EditorInterface); ok {
    //  e.SetEditState(state)
    //}
}

type folder struct {
    *node
    color int
}

var _ NodeInterface = (*folder)(nil)

type document struct {
    *node
    visible bool
    content string
}

var _ NodeInterface = (*document)(nil)
var _ EditorInterface = (*document)(nil)

func (d *document) SetEditState(state bool) {
    d.visible = state
}

我曾多次尝试重构它以实现所需的行为,但没有一种方法让我满意。我不会将它们全部粘贴到问题中,但我已经创建了 Go playground 链接:

示例 2:https://play.golang.org/p/kyG-sRu6z- 在此示例中,测试通过,因为我们将接口(interface)添加为嵌入式结构的“self”成员。这似乎是一个令人讨厌的拼凑。

示例 3:https://play.golang.org/p/Sr5qhLn102 在此示例中,我们将 SetNodeState 移至接受接口(interface)的函数。这样做的缺点是我们无法访问嵌入式结构,因此所有成员都需要在接口(interface)上公开 getter 和 setter。这使界面变得不必要地复杂。

示例 4:https://play.golang.org/p/P5E1kf4dqj 在此示例中,我们提供了一个 getter 来返回整个嵌入式结构,我们在 SetNodeState 函数中使用了它。这似乎又是一场令人讨厌的混战。

示例 5:https://play.golang.org/p/HMH-Y_RstV 在此示例中,我们将接口(interface)作为参数传递给需要它的每个方法。同样,这感觉不对。

示例 6:https://play.golang.org/p/de0iwQ9gGY 在此示例中,我们删除了 NodeInterface,并从基本结构和实现 ItemInterface 的对象构造节点。这可能是这些示例中问题最少的,但仍然让我想要一个更好的解决方案。

也许有人可以提出更好的解决方案?

最佳答案

在这里,我让文档节点重新实现 SetNodeState,并使用 d.node.SetNodeState 来更新节点的状态;在非 Go-y 术语中,我会将特定于类的代码下推到子类,like this :

package main

import "testing"

func main() {
    tests := []testing.InternalTest{{"TestTree", TestTree}}
    matchAll := func(t string, pat string) (bool, error) { return true, nil }
    testing.Main(matchAll, tests, nil, nil)
}

func TestTree(t *testing.T) {
    n := getTestNode()
    n.SetNodeState(true)
    if !n.(*document).visible {
        t.Error("document is not visible")
    }
}

func getTestNode() NodeInterface {
    doc := &document{node: &node{}, content: "foo"}
    folder := &folder{node: &node{children: []NodeInterface{doc}}, color: 123}
    return folder.children[0]
}

type NodeInterface interface {
    SetNodeState(state bool)
}

type node struct {
    open     bool
    parent   NodeInterface
    children []NodeInterface
}

func (n *node) SetNodeState(state bool) {
    n.open = state
}

type folder struct {
    *node
    color int
}

var _ NodeInterface = (*folder)(nil)

type document struct {
    *node
    visible bool
    content string
}

func (d *document) SetNodeState(state bool) {
        d.node.SetNodeState(state)
        d.SetEditState(state)
}

func (d *document) SetEditState(state bool) {
    d.visible = state
}

这还可以让您编写适用于任何节点的通用方法without referring to specific node types ,您可能会发现它比 node 方法具有针对特定类型的类型断言的方法更清晰。

(反过来,这会让你创建一个公共(public) Node/NodeInterface 并将它们保存在与特定节点类型不同的包中,因为特定类型只会取决于一般类型,而不是相反(记忆两个 Go 包 can't both depend on each other )。但是将 node 类型与特定节点类型放在一个包中似乎是合理的做。)


如果上述方法不适用,您的第三个示例(具有采用接口(interface)的函数)之类的方法可能会有所帮助。为了缩短一点,该接口(interface)可能能够提供 getNode() *node 而不是 setOpenappendChild 等,具体取决于关于具体情况。

Go stdlib 导出采用接口(interface)的函数,例如,io.ReadFull(r, buf) 而不是 Reader 具有 ReadFull(buf ) 方法。我怀疑在 C++ 中,将代码放在一个裸函数而不是一个方法中会被认为是不好的形式,但这在 Go 中是一种常见的做法。


因此:有时您可以通过(重新)实现特定类型的方法来获得面向对象的行为;如果不能,接受接口(interface)的函数是惯用的。

关于go - 在 Go 中正确获取多态代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33981867/

相关文章:

go - SQL 查询在服务器上完成但程序永远不会恢复

go - 响应 header 设置不适用于错误情况

go - 带有两个外键的中间模型 : file structure?

go - 如何自动启用测试在包内并行运行?

go - 如何使用text/template预定义的 "call"函数?

go - 在 Go 中不断出现随机 TLS 握手错误

go - 有 url.QueryEscape 的例子和用法吗?对于 golang

inheritance - 用于约束和继承集的接口(interface)

postgresql - 如何使用postgresql角色连接数据库?

go - 为什么我的 Makefile 不在命令中插入表达式