JSON可以清晰地表示一个令牌树(可能由解析器生成)。
访问者模式使用多态性。
为了被访问者模式使用,您必须对其进行反序列化,以获得具有不同Visit行为的对象:
- MethodToken
- OperatorToken
- NameToken
然后IVisitor应该为每个实现Visit方法:
public interface IVisitor
{
void Visit(MethodToken token) { }
void Visit(OperatorToken token) { }
void Visit(NameToken token) { }
}
public interface IVisitable
{
void Accept(IVisitor visitor);
}
public class MethodToken : IVisitable
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
< p > < em > < strong > 补充说明:
< p > < code > Object 是一个非常糟糕的名称,尤其是在 C# 中,因为 < code > Object 是每个类的基类,更不用说冲突了,它并没有传达任何特殊的含义...那令牌呢?
public class Token
{
public string Name { get; set; }
public string Value { get; set; }
public List<Token> Children { get; set; }
}
关于Childs属性...
访问者模式的目的
如果不知道何时/为什么使用螺丝刀,就不应该使用它(顺便说一句,这可能很危险)。
访问者模式有助于避免使用十几个switch case或更糟糕的if else if else(难以维护、阅读困难),同时还可以带来强类型检查的优势。它还有助于将相关代码(高内聚)放在一个类(访问者)中。当然,一旦实现,对象树(此处是标记)的访问者可以由多种类型的访问者访问,只要它们实现了IVisitor
接口。
在您的情况下,您必须首先将每个Token
转换为Token
的强子类型(通过字典映射避免任何if/switch或自定义反序列化)
在您的情况下:
- 首先读取文本(显然它是json格式),并将其转换为对象。我们通常称之为反序列化。这在这里是可能的,因为文本已经采用了已知的正确结构化格式,因此很容易找到词法解析器/语法解析器。(否则,您将不得不编写自己的词法解析器/语法解析器或使用类似于lex/yacc的东西。)
但是,我们必须将文本的每个部分部分反序列化为正确的类型。我们将使用Newtonsoft.Json
来完成此操作:
public abstract class BaseToken : IVisitable
{
public string Value { get; set; }
public List<BaseToken> Children { get; } = new List<BaseToken>();
public abstract void Accept(IVisitor visitor);
}
阅读文本并解析Json:
var text = File.ReadAllText("path/to/my/file.json");
var jsonToken = JObject.Parse(text);
- 我们必须处理
JToken
以提取正确的类实例:
var token = CreateToken(jsonToken);
CreateToken
方法:
private static BaseToken CreateToken(JToken jsonToken)
{
var typeOfToken = jsonToken["Name"];
if (typeOfToken == null || typeOfToken.Type != JTokenType.String)
{
return null;
}
BaseToken result;
switch (typeOfToken.ToString())
{
case "Method":
{
result = jsonToken.ToObject<MethodToken>();
break;
}
case "Operator":
{
result = jsonToken.ToObject<OperatorToken>();
break;
}
default:
{
result = jsonToken.ToObject<NameToken>();
break;
}
}
var jChildrenToken = jsonToken["Childs"];
if (result != null &&
jChildrenToken != null &&
jChildrenToken.Type == JTokenType.Array)
{
var children = jChildrenToken.AsJEnumerable();
foreach (var child in children)
{
var childToken = CreateToken(child);
if (childToken != null)
{
result.Children.Add(childToken);
}
}
}
return result;
}
正如您所看到的,文本中仍然存在一些开关模式。
- 然后调用标记访问器:
var tokenVisitor = new TokenVisitor();
token.Accept(tokenVisitor);
Console.WriteLine(tokenVisitor.Output);
TokenVisitor 代码
internal class TokenVisitor : IVisitor
{
private readonly StringBuilder _builder = new StringBuilder();
private int firstIndex = 1;
private int secondIndex = 0;
private readonly HashSet<BaseToken> _visitedTokens = new HashSet<BaseToken>();
public string Output => _builder.ToString();
public void Visit(MethodToken token)
{
var localFirst = firstIndex;
var localSecond = secondIndex;
firstIndex = 0;
secondIndex = 1;
RenderChild(token.Children, localFirst);
_builder.Append(token.Value);
RenderChild(token.Children, localSecond);
}
private void RenderChild(List<BaseToken> children, int index)
{
if (children.Count > index)
{
_builder.Append("(");
children[index].Accept(this);
_builder.Append(")");
}
}
public void Visit(OperatorToken token)
{
if (token.Children.Count > 0)
{
token.Children[0].Accept(this);
_builder.Append(" ");
}
_builder.Append(token.Value);
if (token.Children.Count > 0)
{
_builder.Append(" ");
token.Children[0].Accept(this);
}
}
public void Visit(NameToken token)
{
if (_visitedTokens.Contains(token))
{
_builder.Append(token.Value);
}
else
{
_visitedTokens.Add(token);
_builder.Append(token.Name);
}
}
}
以上实现旨在满足您的期望(即输出与预期字符串完全相同)。 它可能不是百分之百可靠的。 您可以在GitHub上找到完整的代码。