我正在构建一棵树来解析一些文本,因此我创建了一些节点:
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]);
}
这看起来有点笨拙,并且随着我添加它只会变得更糟。
我是否应该
- 尝试以某种方式将逻辑封装到基
Node
类中,以便我可以只使用 DoStuff()
函数而不必担心类型转换?
- 我是否应该为每个节点类型添加某种枚举,以便我可以使用 switch 而不是 foreach 循环?
- 还有别的事吗?
需要给你们提供更多背景信息。在上面的示例中,变量节点需要访问字典才能呈现自身。
我正在解析一个模板。在模板中,有变量。变量名称及其值存储在字典中。在解析时,我没有值(好吧,我可以,但我希望能够使用不同的值重新渲染模板),所以我需要能够将不同的字典传递给模板。按照我设置的方式,模板执行所有渲染,节点不执行任何操作。我想我可以将字典再传递一个级别到每个节点,以便它们可以渲染自己......
为了详细说明#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();
}
}
我想这也不是一个糟糕的解决方案。
到目前为止的解决方案:
- 使用枚举和 switch
- 构建类型/操作的字典,使 switch 更清晰
- 多态性 (x2)
- 访问者模式(还没读过这篇文章)
- 使用ANTLR——尽管 ANTLR 会构建一棵树,然后这个问题再次出现
I'm building up a tree to parse some text, so I've created some nodes:
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;
}
}
I'm going to add some "branching" nodes pretty soon (nodes that contain other nodes).
I'm wondering about the best way to process them. Right now I've got some code that looks like this:
foreach (var item in nodes)
{
if (item is TextNode)
sb.Append(((TextNode)item).Text);
if (item is VariableNode)
sb.Append(dict[((VariableNode)item).Name]);
}
Which seems a bit clunky, and will only get worse as I add to it.
Should I
- Try to encapsulate the logic into the base
Node
class somehow so that I can just the DoStuff()
function without worrying about type-casting?
- Should I add some kind of enum for each node type so that I can use a switch instead of a foreach loop?
- Something else?
Need to give you guys a bit more context. In the example above, variables nodes need access to the dict to render themselves.
I'm parsing a template. In the template, there are variables. The variable names and their values are stored in the dict. At parse time, I don't have the values (well, I could, but I want to be able to re-render the template with different values), so I need to be able to pass in different dicts to the template. The way I have it set up, the template does all the rendering, the nodes do nothing. I guess I could pass the dict one more level down to each node so they can render themselves...
To elaborate on #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();
}
}
I guess that isn't such a bad solution either.
Solutions so far:
- Use an enum and switch
- Build a dictionary of type/actions to make the switch a bit cleaner
- Polymorphism (x2)
- Visitor pattern (haven't read this yet)
- Use ANTLR -- although ANTLR will build a tree and then this problem applies again
发布评论
评论(5)
您可以改为使用
接口
并定义enum
属性,例如。NodeType Type
,其中Type
是一个enum
,其值为TextNode
、VariableNode
。不过,这里您还需要转换节点才能获取值。您可以尝试添加一个名为Value
的属性,该属性根据NodeType
返回Text
或Name
。如果可以从VariableNode
访问dict
,则Value
可以返回您的方法所需的确切值。总而言之,我认为接口比抽象类更适合您的需求。
You can instead use an
interface
and define aenum
property, for eg.NodeType Type
whereType
is anenum
with valuesTextNode
,VariableNode
. Although, here also you will need to cast the node to get the value. You can try to add a property namedValue
which returnsText
orName
depending on theNodeType
. Ifdict
is accessible fromVariableNode
,Value
can return the exact value needed for your method.All in all, I think interfaces suit your requirements better than an abstract class.
如果您将拥有许多具有不同行为的不同节点类型,我当然会考虑使用基类 Node,然后为实现特定行为的每种类型实现一个专门的节点类。
将树处理逻辑放入节点的另一种方法是使用访问者模式。在这种情况下,您往往会拥有更同质的树结构,并在树外部维护特定的处理规则。
如果这棵树是为了解析文本的目的,你看过 Antlr 吗?该工具可以根据指定的语法为您生成语法树。
If you're going to have lots of different node types with different behaviours I would certainly consider using a base class Node and then implementing a specialized node class for each type that implements the specific behaviour.
An alternative to putting tree processing logic into the nodes is to use the visitor pattern. In this case you would tend to have a more homogeneous tree structure with the specific processing rules maintained outside the tree.
If this tree is for the purpose of parsing text have you looked at Antlr? This tool can generate your syntax tree for you, based on a specified grammar.
将 foreach(var item in Nodes) 移动到抽象节点类中的方法(即 BuildAll)中,创建一个抽象/虚拟方法 Build(在抽象 Node 类中)并调用 BuildAll 中的方法 Build() 并让它输出一个字符串...
像这样:
并在节点的每个实际实现中覆盖它...
所以 TextNode 将有...
在这种情况下,您的基类不需要知道其后代的确切实现,因此不会违反开闭原则。
Move your foreach(var item in nodes) into a method (i.e. BuildAll) in the abstract node class, create an abstract / virtual method Build (in the abstract Node class) and call the method Build() in the BuildAll and let it output a string...
Like so:
and override it inside each actual implementation of node...
so TextNode will have...
In this case, your base class does not need to know the exact implementation of its descendant, thus does not break the Open Close Principle.
您可以创建一个
Dictionary>
- 键是节点类型和Action
委托 是您对每个委托执行的操作。您可以简单地执行以下操作:
以便为每种类型执行正确的操作。
You can create a
Dictionary<Type,Action<T>>
- the keys are the node types and theAction<T>
delegates are what you do with each.You can than simply do:
In order to execute the right action for each type.
多态性正是您想要的。像这样的:
所以你可以:
Polymorphism is exactly what you want. Something like this:
So you can: