在c#中填充树结构的优雅且可维护的方式

时间:2009-02-18 23:23:59

标签: c# design-guidelines

我有一棵树。

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>();
}

我想填充一个大的用于单元测试目的。我真的很喜欢干什么东西。

为了便于说明,我的树具有以下结构

Parent,desc 
  Child 1, desc1
    Grandchild 1, desc1 
  Child 2, desc2

你会如何以优雅和可维护的方式填充树木?

我觉得这段代码非常重复且容易出错:

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);

修改

我最终做了DSL方法:

演示test lives here

implementation is here

它使用构建器和简单的DSL。

5 个答案:

答案 0 :(得分:3)

你可以编写一个带有状态的“TreeBuilder”来保存一些连接混乱:

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()

另一种方法是用一些简单的格式发明一个简单的配置文件/字符串。

答案 1 :(得分:2)

嵌套构造可能是一个很好的选择。不要暴露孩子名单的好主意。

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>();
}

答案 2 :(得分:2)

  • 理想情况下,您希望有一种方法将语言扩展为自定义类型的文字。 C#没有这个,所以你必须找到另一种方法。

  • 您可以制作内部DSL ,通常使用流畅的界面

  • 关注XElement example of functional construction

  • 使用自定义解析器创建外部DSL 。如果您仔细设计语言,解析器可以很容易。

  • 使用 XML 。基本上这是一种创建外部DSL并免费获得解析器的方法。

外部DSL选项很不错,因为当你阅读它们时,你知道只有数据,并且不必担心理解代码结构。此外,数据是文件,文件是数据。这样可以通过更改文件轻松交换数据,并且更容易准备好文件更改历史记录。最后,当非程序员提供数据时,外部DSL就很好。

这里的权衡是时间与价值。 您需要提供多少数据/更改频率/更改者是您必须回答的问题。

答案 3 :(得分:1)

您可以使用填充树的简单解析器编写树内容的简单XML表示。以下将给出您在上面指定的结构。

<Node description="desc">
    Parent
    <Node description="desc1">
        Child 1
        <Node description="desc1">
            Grandchild 1
        </Node>
    </Node>
    <Node description="desc2">
        Child 2
    </Node>
</Node>

答案 4 :(得分:1)

我会将实现拆分为TreeClass和TreeNodeClass

树类将具有成员变量

TreeNodeClass root

方法

TreeNodeClass addAtRoot(data) 

返回他们刚刚创建的节点

TreeNodeClass还需要一个AddChild()方法,它还会返回刚刚添加的节点。

然后你可以做类似

的事情
addAtRoot(rootData).AddChild(childData).AddChild(grandchildData);

使用类似的东西随机生成树

AddRecursively(TreeNodeClass root)
{
    numChildren = SomeRandomNumber;
    While(numChildren > 0)
    {
       CTreeNodeClass newnode = root.AddChild(SomeRandomData);
       AddRecursively(newnode);
    }
}

主要思想是您想要返回刚刚添加到树中的节点。

你可能也想让孩子知道它的父母,因为这有时非常方便。