Элегантный и понятный способ заполнения древовидных структур в c # - PullRequest
2 голосов
/ 19 февраля 2009

У меня есть дерево.

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

EDIT

Я закончил использовать подход DSL:

Демо тест живет здесь .

Реализация здесь .

Он использует компоновщик и простой DSL.

Ответы [ 5 ]

3 голосов
/ 19 февраля 2009

Вы можете написать «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()

Другой способ - изобрести простой файл / строку конфигурации с простым форматом.

2 голосов
/ 19 февраля 2009
  • В идеале вам нужен способ расширить язык до литералов пользовательских типов . В C # этого нет, поэтому вам нужно найти другой подход.

  • Можно изготовить внутренний DSL , обычно с плавным интерфейсом .

  • Следуйте XElement примеру функциональной конструкции .

  • Создайте внешний DSL с пользовательским анализатором. Если вы тщательно продумываете язык, парсер может быть простым.

  • Использовать XML . По сути, это способ создать внешний DSL и получить парсер бесплатно.

Внешние опции DSL хороши тем, что когда вы их читаете, вы знаете, что только данные , и вам не нужно беспокоиться о том, чтобы разобраться в конструкциях кода. Кроме того, данные - это файл, а файл - это данные. Это облегчает обмен данными путем изменения файлов и упрощает подготовку истории изменений файлов. Наконец, и внешний DSL хорош, когда данные будут не программистом.

Компромисс здесь - время против стоимости. Сколько данных у вас будет / как часто они будут меняться / кто изменит их - это вопросы, на которые вы должны ответить.

2 голосов
/ 19 февраля 2009

Вложенная конструкция может быть хорошим вариантом здесь. Хорошая идея не выставлять список детей тоже.

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>();
}
1 голос
/ 19 февраля 2009

Я бы разделил реализацию на 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);
    }
}

Основная идея заключается в том, что вы хотите вернуть узел, который вы только что добавили в дерево.

Возможно, вы также захотите, чтобы ребенок знал своего родителя, поскольку иногда это может быть очень удобно.

1 голос
/ 19 февраля 2009

Вы можете написать простое XML-представление содержимого дерева с помощью простого синтаксического анализатора, который заполняет дерево. Следующее даст структуру, которую вы указали выше.

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