问题
I have a Tree.
class TreeNode {
public TreeNode(string name, string description) {
Name = name;
Description = description;
}
string Name { get; set; }
string Description { get; set; }
public List<TreeNode> Children = new List<TreeNode>();
}
I would like to populate a large one for unit testing purposes. I would really like to keep stuff DRY.
Say for illustration purposes my tree has the following structure
Parent,desc Child 1, desc1 Grandchild 1, desc1 Child 2, desc2
How would you go about populating the tree in an elegant an maintainable way?
I find this code quite repetitious and error prone:
var parent = new TreeNode("Parent", "desc");
var child1 = new TreeNode("Child 1", "desc1");
var child2 = new TreeNode("Child 2", "desc2");
var grandchild1 = new TreeNode("Grandchild 1", "desc1");
parent.Children.Add(child1);
parent.Children.Add(child2);
child1.Children.Add(grandchild1);
EDIT
I ended up doing the DSL approach:
A demo test lives here.
The implementation is here.
It uses a builder and a simple DSL.
回答1:
Ideally you want a way to extend the language to literals of custom types. C# doesn't have this, so you have to find another approach.
You can fabricate an internal DSL, typically with a fluent interface.
Follow the XElement example of functional construction.
Create an external DSL with a custom parser. If you design the language carefully, the parser can be easy.
Use XML. Basically this is a way to create an external DSL and get the parser for free.
The external DSL options are nice because when you read them, you know there's only data, and don't have to worry about making sense of code constructs. Also, the data is the file, and the file is the data. That makes it easy to swap data around by changing files, and easier to ready file change histories. Finally, and external DSL is good when a non-programmer will be supplying the data.
The tradeoff here is time vs. value. How much data you will have / how often it will change / who will change it are the questions you have to answer.
回答2:
You can write a "TreeBuilder" with a state to save some connections mess:
TreeBuilder builder = new TreeBuilder();
builder.AddNode("Parent", "desc"); // Adds a node, and sets the cursor to it
builder.AddLeaf("Child 1", "desc1"); // Adds a node and leaves the cursor at the Parent
builder.AddNode("Child 2", "desc2");
builder.AddLeaf("Grandchild 1", "desc1");
builder.Up(); // Moves the cursor to the parent
builder.AddNode("Child 3", "desc3");
root = builder.GetRoot()
Another way is to invent a simple configuration file/string with some simple format.
回答3:
Nested construction could be a good option here. Good idea to not expose the list of children too.
class Program
{
static void Main(string[] args)
{
var parent =
new TreeNode( "Parent", "desc", new TreeNode[] {
new TreeNode( "Child 1", "desc1", new TreeNode[] {
new TreeNode( "Grandchild 1", "desc1" ) } ),
new TreeNode( "Child 2", "desc2" ) } );
}
}
class TreeNode
{
public TreeNode(string name, string description, IEnumerable<TreeNode> children)
: this(name, description)
{
_children.AddRange(children);
}
public TreeNode(string name, string description)
{
Name = name;
Description = description;
}
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<TreeNode> Children
{
get
{
return _children.AsReadOnly();
}
set
{
_children.Clear();
_children.AddRange(value);
}
}
private List<TreeNode> _children = new List<TreeNode>();
}
回答4:
You could write up a simple XML representation of the tree contents with a simple parser that populates the tree. The following would give the structure you specified above.
<Node description="desc">
Parent
<Node description="desc1">
Child 1
<Node description="desc1">
Grandchild 1
</Node>
</Node>
<Node description="desc2">
Child 2
</Node>
</Node>
回答5:
I would split the implementation into a TreeClass and a TreeNodeClass
The Tree Class would have member variables
TreeNodeClass root
with methods
TreeNodeClass addAtRoot(data)
Which return the node they just created
The TreeNodeClass Also needs an AddChild() Method, which would also return the node it just added.
Then you can do something like
addAtRoot(rootData).AddChild(childData).AddChild(grandchildData);
or
Use something like this to randomly generate a tree
AddRecursively(TreeNodeClass root)
{
numChildren = SomeRandomNumber;
While(numChildren > 0)
{
CTreeNodeClass newnode = root.AddChild(SomeRandomData);
AddRecursively(newnode);
}
}
The main idea is that you want to return the Node that you just added to the tree.
You might also want to make the child know its parent as that can be quite handy sometimes.
来源:https://stackoverflow.com/questions/563328/elegant-and-maintainable-way-of-populating-tree-structures-in-c-sharp