如何使用节点?

时间:2011-01-12 08:21:57

标签: c# design-patterns

我正在构建一个树来解析一些文本,所以我创建了一些节点:

abstract class Node { }

class TextNode : Node
{
    public readonly string Text;
    public TextNode(string text)
    {
        Text = text;
    }
    public TextNode(char ch)
    {
        Text = ch.ToString();
    }
}

class VariableNode : Node
{
    public readonly string Name;
    public VariableNode(string name)
    {
        Name = name;
    }
}

我很快就会添加一些“分支”节点(包含其他节点的节点)。

我想知道处理它们的最佳方法。现在我有一些看起来像这样的代码:

foreach (var item in nodes)
{
    if (item is TextNode)
        sb.Append(((TextNode)item).Text);
    if (item is VariableNode)
        sb.Append(dict[((VariableNode)item).Name]);
}

这似乎有点笨拙,只会在我加入时变得更糟。

我应该

  1. 尝试以某种方式将逻辑封装到基础Node类中,以便我可以只使用DoStuff()函数而不必担心类型转换?
  2. 我应该为每个节点类型添加某种枚举,以便我可以使用开关而不是foreach循环吗?
  3. 别的什么?

  4. 需要给你们更多的背景信息。在上面的示例中,变量节点需要访问dict来呈现自己。

    我正在解析模板。在模板中,有变量。变量名称及其值存储在dict中。在解析时,我没有值(好吧,我可以,但我希望能够使用不同的值重新呈现模板),因此我需要能够将不同的值传递给模板。我设置它的方式,模板完成所有渲染,节点什么都不做。我想我可以将dict更多级别传递给每个节点,以便他们自己渲染...


    详细说明#1

    // in class Template
    public string Render(Dictionary<string, object> dict)
    {
        var sb = new StringBuilder();
    
        foreach (var item in nodes)
            sb.Append(item.Render(dict));
    
        return sb.ToString();
    }
    
    interface INode {
        string Render(Dictionary<string, object> dict);
    }
    
    class TextNode : INode
    {
        private string _text;
    
        public TextNode(string text)
        {
            _text = text;
        }
        public TextNode(char ch)
        {
            _text = ch.ToString();
        }
    
        public string Render(Dictionary<string, object> dict)
        {
            return _text;
        }
    }
    
    class VariableNode : INode
    {
        private string _name;
    
        // TODO: filters...
    
        public VariableNode(string name)
        {
            _name = name;
        }
    
        public string Render(Dictionary<string, object> dict)
        {
            return dict[_name].ToString();
        }
    }
    

    我想这也不是一个糟糕的解决方案。


    到目前为止的解决方案:

    1. 使用枚举并切换
    2. 构建类型/操作字典以使开关更清洁
    3. 多态性(x2)
    4. Visitor pattern(尚未阅读此内容)
    5. 使用ANTLR - 虽然ANTLR会构建一个树,然后再次出现此问题

5 个答案:

答案 0 :(得分:2)

您可以改为使用interface并定义enum属性,例如。 NodeType Type其中Typeenum,其值为TextNodeVariableNode。虽然,在这里您还需要强制转换节点以获取值。您可以尝试添加名为Value的属性,该属性会返回TextName,具体取决于NodeType。如果dict可以访问VariableNode,则Value可以返回您的方法所需的确切值。

总而言之,我认为界面比抽象类更适合您的要求。

答案 1 :(得分:2)

如果您将要有许多具有不同行为的不同节点类型,我当然会考虑使用基类Node,然后为实现特定行为的每种类型实现专门的节点类。

将树处理逻辑放入节点的替代方法是使用visitor pattern。在这种情况下,您将倾向于使用更加同质的树结构,并在树外维护特定的处理规则。

如果此树是用于解析文本的,您是否查看了Antlr?此工具可以根据指定的语法为您生成语法树。

答案 2 :(得分:2)

将foreach(节点中的var项)移动到抽象节点类中的方法(即BuildAll)中,创建抽象/虚方法Build(在抽象Node类中)并在BuildAll中调用方法Build()并让它输出一个字符串......

像这样:

    public abstract string Build();

    public string BuildAll() {
      var output = new StringBuilder();
      foreach(var node in nodes) {
        output.append(node.Build()); 
      }
      return output.toString();
    }

并在节点的每个实际实现中覆盖它... 所以TextNode会......

public override string Build() {
  return ... extract whatever you want from TextNode;
}

在这种情况下,您的基类不需要知道其后代的确切实现,因此不会破坏开放关闭原则。

答案 3 :(得分:1)

多态性正是你想要的。像这样:

class Node
{
   public virtual string ToDisplayText(params object[] parameters)
   {
      return string.Empty;
   }
}
class TextNode : Node
{
   public override string ToDisplayText(params object[] parameters)
   {
      return this.Text;
   }
}
class VariableNode : Node
{
   public override string ToDisplayText(params object[] parameters)
   {
      //check parameters
      var dict = (Dictionary<string,string>)parameters[0];
      return dict[this.Name];
   }
}

所以你可以:

foreach(var node in nodes)
{
   sb.Append(node.ToDisplayText(dict));
}

答案 4 :(得分:1)

您可以创建Dictionary<Type,Action<T>> - 密钥是节点类型,Action<T> delegates是您对每个密钥执行的操作。

你可以这样做:

Dictionary[typeof(variable)];

为每种类型执行正确的操作。