How to work with nodes?
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 theDoStuff()
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<str开发者_如何学Pythoning, 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
You can instead use an interface
and define a enum
property, for eg. NodeType Type
where Type
is an enum
with values TextNode
, VariableNode
. Although, here also you will need to cast the node to get the value. You can try to add a property named Value
which returns Text
or Name
depending on the NodeType
. If dict
is accessible from VariableNode
, Value
can return the exact value needed for your method.
All in all, I think interfaces suit your requirements better than an abstract class.
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.
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:
public abstract string Build();
public string BuildAll() {
var output = new StringBuilder();
foreach(var node in nodes) {
output.append(node.Build());
}
return output.toString();
}
and override it inside each actual implementation of node... so TextNode will have...
public override string Build() {
return ... extract whatever you want from TextNode;
}
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.
You can create a Dictionary<Type,Action<T>>
- the keys are the node types and the Action<T>
delegates are what you do with each.
You can than simply do:
Dictionary[typeof(variable)];
In order to execute the right action for each type.
Polymorphism is exactly what you want. Something like this:
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];
}
}
So you can:
foreach(var node in nodes)
{
sb.Append(node.ToDisplayText(dict));
}
精彩评论