I've refactored my tree package several times, and not found a solution I'm happy with, so I'd like a little advice on the best way to proceed.
I've tried to reduce the problem down to it's essence, and made a simple example of a tree which is composed of nodes. All nodes have a set of common functionality (expressed in the example as an open / closed state). In addition, there are several types of nodes, each with specialist behaviours (expressed in the example as editable nodes implementing EditorInterface and having a visible / hidden state).
In my example we try to satisfy the desired behaviour - any node can be opened, and when it's opened if it's editable, it should make the editor visible.
My example defines two types of node, folders and documents. Documents are editable.
My instinct is to define a struct for node, and include common functionality as members and methods. Then define structs for folder and document, with an embedded anonymous node struct in each.
However, this causes a problem which will be highlighted by my first example. I've created a simple test that fails:
Example 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
}
I've tried to refactor this several times to achieve the desired behaviour, but none of the methods makes me happy. I won't paste them all into the question, but I've created Go playground links:
Example 2: https://play.golang.org/p/kyG-sRu6z- In this example the test passes, because we add the interface as the "self" member of the embedded struct. This seems like a nasty kludge.
Example 3: https://play.golang.org/p/Sr5qhLn102 In this example, we move SetNodeState to a function that accepts the interface. The disadvantage of this is that we don't have access to the embedded struct, so all members need getters and setters exposed on the interface. This makes the interface needlessly complex.
Example 4: https://play.golang.org/p/P5E1kf4dqj In this example, we provide a getter to return the entire embedded struct, which we use in the SetNodeState function. Again this seems like a nasty kludge.
Example 5: https://play.golang.org/p/HMH-Y_RstV In this example we pass in the interface as a parameter to every method that needs it. Again, this doesn't feel right.
Example 6: https://play.golang.org/p/de0iwQ9gGY In this example, we remove NodeInterface, and construct nodes from a base struct and an object implementing ItemInterface. This is perhaps the least problematic of the examples, but still leaves me wanting a better solution.
Perhaps someone can suggest a better solution?