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

发布于 2024-07-13 10:42:23 字数 1299 浏览 3 评论 0原文

我有一棵树。

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方法:

演示测试位于此处

实现在这里

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

平定天下 2024-07-20 10:42:23

您可以编写一个带有状态的“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()

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

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.

总攻大人 2024-07-20 10:42:23
  • 理想情况下,您需要一种将语言扩展到自定义类型的文字的方法。 C#没有这个,所以你必须寻找另一种方法。

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

  • 遵循 XElement 示例 功能构造

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

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

外部 DSL 选项很好,因为当您阅读它们时,您知道其中只有数据,并且不必担心理解代码结构。 另外,数据就是文件,文件就是数据。 这使得通过更改文件来交换数据变得容易,并且更容易准备文件更改历史记录。 最后,当非程序员提供数据时,外部 DSL 是很好的选择。

这里的权衡是时间与价值。 您将拥有多少数据/更改的频率/谁将更改是您必须回答的问题。

  • 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.

只是在用心讲痛 2024-07-20 10:42:23

嵌套结构在这里可能是一个不错的选择。 不要公开孩子的名单也是个好主意。

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

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>();
}
沫尐诺 2024-07-20 10:42:23

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

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

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>
失退 2024-07-20 10:42:23

我会将实现分为 TreeClass 和 TreeNodeClass

Tree Class 将具有成员变量

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

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

您可能还想让孩子认识其父母,因为有时这会很方便。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文