在Go

时间:2015-11-29 10:44:40

标签: 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- 在这个例子中,测试通过了,因为我们将接口添加为" self"嵌入式结构的成员。这看起来像是一个令人讨厌的垃圾。

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

示例4:https://play.golang.org/p/P5E1kf4dqj 在这个例子中,我们提供了一个getter来返回我们在SetNodeState函数中使用的整个嵌入式结构。再次,这似乎是一个讨厌的kludge。

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

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

也许有人可以建议更好的解决方案?

1 个答案:

答案 0 :(得分:0)

在这里,我将重新实现文档节点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
}

这也允许您编写适用于任何node without referring to specific node types的常规方法,您可能会发现这些方法比node方法具有特定类型的类型断言的方法更清晰。

(反过来,这会让你公开Node / NodeInterface并将它们保存在与特定节点类型不同的包中,因为特定的类型只取决于一般类型和从来没有反过来(回想两个Go包can't both depend on each other)。但是将node类型保存在具有特定节点类型的包中似乎是合理的。)

如果上述方法不适用,那么类似于您的第三个示例(具有接口的功能)可能有所帮助。为了缩短它,该界面可能只提供getNode() *node而不是setOpenappendChild等,具体取决于具体情况。

Go stdlib导出带有接口的函数,例如io.ReadFull(r, buf)而不是具有Reader方法的ReadFull(buf)。我怀疑它在C ++中被认为是不好的形式,因为代码是在一个简单的函数而不是一个方法,但它在Go中是常见的做法。

所以:有时你可以通过(重新)实现特定类型的方法来获得OO-ish行为;当你不能,接受界面的功能是惯用的。