我已经多次重构我的树包,但没有找到我满意的解决方案,所以我想要一些关于最佳继续进行的建议。
我试图将问题简化为它的本质,并制作了一个由节点组成的树的简单示例。所有节点都有一组通用功能(在示例中表示为打开/关闭状态)。此外,还有几种类型的节点,每一种都有专门的行为(在示例中表示为实现 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
而不是 setOpen
、appendChild
等,具体取决于关于具体情况。
Go stdlib 导出采用接口(interface)的函数,例如,io.ReadFull(r, buf)
而不是 Reader
具有 ReadFull(buf )
方法。我怀疑在 C++ 中,将代码放在一个裸函数而不是一个方法中会被认为是不好的形式,但这在 Go 中是一种常见的做法。
因此:有时您可以通过(重新)实现特定类型的方法来获得面向对象的行为;如果不能,接受接口(interface)的函数是惯用的。
关于go - 在 Go 中正确获取多态代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33981867/