在 C# 中实现访问者模式
Implementing visitor Pattern in C#
我是这个模式的新手,有人可以帮我吗?
我得到了一个这样的对象:
public class Object
{
public string Name { get; set; }
public object Value { get; set; }
public List<Object> Childs { get; set; }
}
这是一个 JSON 示例:
{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "5",
"Childs": []
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "6",
"Childs": []
}]
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "3",
"Childs": []
}]
}]
}
我的问题是如何制作访问者模式以获得最终字符串:
(Name IsEqual 3)And((Name IsEqul 5)And(Name IsEqual 6))
这可能不是您想要的。但是在不使用访问者模式的情况下创建所需输出的一种方法是将以下方法添加到 Object
class,如下所示:
public string Format()
{
if (Name == "Operator")
{
if(Childs == null || Childs.Count != 1)
throw new Exception("Invalid Childs");
Object chlid = Childs[0];
return chlid.Name + " IsEqual " + chlid.Value;
}
if (Name == "Method")
{
if(Childs == null || Childs.Count == 0)
throw new Exception("Invalid Childs");
var str = " " + Value + " ";
return string.Join(str, Childs.Select(x => "(" + x.Format() + ")"));
}
throw new Exception("Format should only be invoked on Operator/Method");
}
首先,您在 result.Second 中的顺序有误,有时您在 result.Final 中遗漏了括号,应该是:
(((Name IsEqual 5) And (Name IsEqual 6)) And (Name IsEqual 3))
要完成此任务,您应该使用递归函数。
static IEnumerable<string> ReturnString(Obj val)
{
foreach (Obj node in val.Childs)
yield return ConvertToString(node);
}
static string ConvertToString(Obj val)
{
switch(val.Name)
{
case "Operator":
{
return string.Format("({0} {1} {2})", val.Childs[0].Name, val.Value, val.Childs[0].Value);
}
case "Method":
{
IEnumerable<string> coll = ReturnString(val);
StringBuilder final = new StringBuilder();
final.Append("(");
IEnumerator<string> e = coll.GetEnumerator();
e.MoveNext();
final.Append(string.Format("{0}", e.Current, val.Value));
while (e.MoveNext())
{
final.Append(string.Format(" {0} {1}", val.Value, e.Current));
}
final.Append(")");
return final.ToString();
}
case "Name":
return Convert.ToString(val.Value);
}
return "-";
}
下面是您在代码中的示例:
string s = ConvertToString(new Obj
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="5",
Childs=null
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="6",
Childs=null
}
}
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="3",
Childs=null
}
}
}
}
});
要实现访问者模式,您需要两个简单的接口
IVisitable
与 Accept
方法具有 IVisitor
作为参数。
IVisitor
IVisitable
的每个实现都有许多 Visit
方法
访问者模式的基本思想是根据实现类型动态改变行为。
对于你的情况,你想要访问的东西(可访问的)是 Object
class 显然没有不同的衍生物,你想根据 属性 值不是类型。所以访问者模式并不是你真正需要的,我强烈建议你用递归的方法来考虑答案。
但是如果你真的想在这里使用访问者模式,它可能看起来像这样。
interface IVisitable { void Accept(IVisitor visitor); }
interface IVisitor {
void VisitAnd(Object obj);
void VisitEquals(Object obj);
}
由于 Object
class 是一个简单的 POCO,我假设您不想实现接口并向此 class 添加方法。所以你需要一个 adapter 对象来适应 Object
到 IVisitable
class VisitableObject : IVisitable {
private Object _obj;
public VisitableObject(Object obj) { _obj = obj; }
public void Accept(IVisitor visitor) {
// These ugly if-else are sign that visitor pattern is not right for your model or you need to revise your model.
if (_obj.Name == "Method" && _obj.Value == "And") {
visitor.VisitAnd(obj);
}
else if (_obj.Name == "Method" && _obj.Value == "IsEqual") {
visitor.VisitEquals(obj);
}
else
throw new NotSupportedException();
}
}
}
public static ObjectExt {
public static IVisitable AsVisitable(this Object obj) {
return new VisitableObject(obj);
}
}
最后,访客实现可能如下所示
class ObjectVisitor : IVisitor {
private StringBuilder sb = new StringBuilder();
public void VisitAnd(Object obj) {
sb.Append("(");
var and = "";
foreach (var child in obj.Children) {
sb.Append(and);
child.AsVisitable().Accept(this);
and = "and";
}
sb.Append(")");
}
public void VisitEquals(Object obj) {
// Assuming equal object must have exactly one child
// Which again is a sign that visitor pattern is not bla bla...
sb.Append("(")
.Append(obj.Children[0].Name);
.Append(" Equals ");
.Append(obj.Children[0].Value);
.Append(")");
}
}
JSON 清楚地表示一个标记树(可能由解析器生成)。
访问者模式使用多态性。
为了被访问者模式使用,您必须反序列化它以获得具有不同访问行为的对象:
- 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);
}
}
补充说明:
Object
是一个非常糟糕的名字,尤其是在 C# 中,因为 Object
是每个 classes 的基础 class,更不用说冲突了,它没有没有传达任何特殊含义...令牌呢?
public class Token
{
public string Name { get; set; }
public string Value { get; set; }
public List<Token> Children { get; set; }
}
访问者的目的
如果您不知道如何使用螺丝刀,则不应使用它 when/why(顺便说一句,它可能很危险)。
访问者模式有助于避免 'ugly'/难以 maintain/painful 阅读十几个开关案例或更糟糕的 if else if else
,同时为您提供强大的类型检查优势。它还有助于将相关代码(高内聚)保持在一个 class(访问者)中。当然,一旦实现,对象树(这里是标记)可以被多种类型的访问者访问,只要他们实现了 IVisitor
接口。
在您的情况下,您必须首先将每个 Token
转换为 Token
的强子类型(通过字典映射以避免任何 if/switch 或自定义反序列化)
你的情况:
- 首先阅读文本(显然是json格式)并将其转换为对象。我们通常称之为反序列化。这是可能的,因为文本已经用众所周知的正确结构化格式格式化,很容易找到 lexer/parser。 (否则你必须自己编写 lexer/parser 或使用类似 lex/yacc 的东西)。
但是,我们必须将文本的每个部分部分反序列化为正确的类型。
我们将使用 Newtonsoft.Json
来执行此操作:
// We define a base class abstract (it cannot be instantiated and we can enforce implementation of methods like the Accept()
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:
// Load text in memory
var text = File.ReadAllText("path/to/my/file.json");
// Get Token instance
var jsonToken = JObject.Parse(text);
- 我们必须处理
JToken
以提取正确的class实例:
// Get the strong typed tree of token
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;
}
如您所见,文本上仍有一些切换模式。
- 然后调用令牌访客:
// Create the visitor
var tokenVisitor = new TokenVisitor();
// Visit the tree with visitor
token.Accept(tokenVisitor);
// Output the result
Console.WriteLine(tokenVisitor.Output);
代码TokenVisitor
internal class TokenVisitor : IVisitor
{
private readonly StringBuilder _builder = new StringBuilder();
// invert the order of children first
private int firstIndex = 1;
private int secondIndex = 0;
// Keep track of name tokens
private readonly HashSet<BaseToken> _visitedTokens = new HashSet<BaseToken>();
public string Output => _builder.ToString();
public void Visit(MethodToken token)
{
// Store local to avoid recursive call;
var localFirst = firstIndex;
var localSecond = secondIndex;
// back to normal order of children
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
上找到完整代码
我是这个模式的新手,有人可以帮我吗?
我得到了一个这样的对象:
public class Object
{
public string Name { get; set; }
public object Value { get; set; }
public List<Object> Childs { get; set; }
}
这是一个 JSON 示例:
{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "5",
"Childs": []
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "6",
"Childs": []
}]
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "3",
"Childs": []
}]
}]
}
我的问题是如何制作访问者模式以获得最终字符串:
(Name IsEqual 3)And((Name IsEqul 5)And(Name IsEqual 6))
这可能不是您想要的。但是在不使用访问者模式的情况下创建所需输出的一种方法是将以下方法添加到 Object
class,如下所示:
public string Format()
{
if (Name == "Operator")
{
if(Childs == null || Childs.Count != 1)
throw new Exception("Invalid Childs");
Object chlid = Childs[0];
return chlid.Name + " IsEqual " + chlid.Value;
}
if (Name == "Method")
{
if(Childs == null || Childs.Count == 0)
throw new Exception("Invalid Childs");
var str = " " + Value + " ";
return string.Join(str, Childs.Select(x => "(" + x.Format() + ")"));
}
throw new Exception("Format should only be invoked on Operator/Method");
}
首先,您在 result.Second 中的顺序有误,有时您在 result.Final 中遗漏了括号,应该是:
(((Name IsEqual 5) And (Name IsEqual 6)) And (Name IsEqual 3))
要完成此任务,您应该使用递归函数。
static IEnumerable<string> ReturnString(Obj val)
{
foreach (Obj node in val.Childs)
yield return ConvertToString(node);
}
static string ConvertToString(Obj val)
{
switch(val.Name)
{
case "Operator":
{
return string.Format("({0} {1} {2})", val.Childs[0].Name, val.Value, val.Childs[0].Value);
}
case "Method":
{
IEnumerable<string> coll = ReturnString(val);
StringBuilder final = new StringBuilder();
final.Append("(");
IEnumerator<string> e = coll.GetEnumerator();
e.MoveNext();
final.Append(string.Format("{0}", e.Current, val.Value));
while (e.MoveNext())
{
final.Append(string.Format(" {0} {1}", val.Value, e.Current));
}
final.Append(")");
return final.ToString();
}
case "Name":
return Convert.ToString(val.Value);
}
return "-";
}
下面是您在代码中的示例:
string s = ConvertToString(new Obj
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="5",
Childs=null
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="6",
Childs=null
}
}
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="3",
Childs=null
}
}
}
}
});
要实现访问者模式,您需要两个简单的接口
IVisitable
与Accept
方法具有IVisitor
作为参数。IVisitor
IVisitable
的每个实现都有许多
Visit
方法
访问者模式的基本思想是根据实现类型动态改变行为。
对于你的情况,你想要访问的东西(可访问的)是 Object
class 显然没有不同的衍生物,你想根据 属性 值不是类型。所以访问者模式并不是你真正需要的,我强烈建议你用递归的方法来考虑答案。
但是如果你真的想在这里使用访问者模式,它可能看起来像这样。
interface IVisitable { void Accept(IVisitor visitor); }
interface IVisitor {
void VisitAnd(Object obj);
void VisitEquals(Object obj);
}
由于 Object
class 是一个简单的 POCO,我假设您不想实现接口并向此 class 添加方法。所以你需要一个 adapter 对象来适应 Object
到 IVisitable
class VisitableObject : IVisitable {
private Object _obj;
public VisitableObject(Object obj) { _obj = obj; }
public void Accept(IVisitor visitor) {
// These ugly if-else are sign that visitor pattern is not right for your model or you need to revise your model.
if (_obj.Name == "Method" && _obj.Value == "And") {
visitor.VisitAnd(obj);
}
else if (_obj.Name == "Method" && _obj.Value == "IsEqual") {
visitor.VisitEquals(obj);
}
else
throw new NotSupportedException();
}
}
}
public static ObjectExt {
public static IVisitable AsVisitable(this Object obj) {
return new VisitableObject(obj);
}
}
最后,访客实现可能如下所示
class ObjectVisitor : IVisitor {
private StringBuilder sb = new StringBuilder();
public void VisitAnd(Object obj) {
sb.Append("(");
var and = "";
foreach (var child in obj.Children) {
sb.Append(and);
child.AsVisitable().Accept(this);
and = "and";
}
sb.Append(")");
}
public void VisitEquals(Object obj) {
// Assuming equal object must have exactly one child
// Which again is a sign that visitor pattern is not bla bla...
sb.Append("(")
.Append(obj.Children[0].Name);
.Append(" Equals ");
.Append(obj.Children[0].Value);
.Append(")");
}
}
JSON 清楚地表示一个标记树(可能由解析器生成)。
访问者模式使用多态性。
为了被访问者模式使用,您必须反序列化它以获得具有不同访问行为的对象:
- 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);
}
}
补充说明:
Object
是一个非常糟糕的名字,尤其是在 C# 中,因为 Object
是每个 classes 的基础 class,更不用说冲突了,它没有没有传达任何特殊含义...令牌呢?
public class Token
{
public string Name { get; set; }
public string Value { get; set; }
public List<Token> Children { get; set; }
}
访问者的目的
如果您不知道如何使用螺丝刀,则不应使用它 when/why(顺便说一句,它可能很危险)。
访问者模式有助于避免 'ugly'/难以 maintain/painful 阅读十几个开关案例或更糟糕的 if else if else
,同时为您提供强大的类型检查优势。它还有助于将相关代码(高内聚)保持在一个 class(访问者)中。当然,一旦实现,对象树(这里是标记)可以被多种类型的访问者访问,只要他们实现了 IVisitor
接口。
在您的情况下,您必须首先将每个 Token
转换为 Token
的强子类型(通过字典映射以避免任何 if/switch 或自定义反序列化)
你的情况:
- 首先阅读文本(显然是json格式)并将其转换为对象。我们通常称之为反序列化。这是可能的,因为文本已经用众所周知的正确结构化格式格式化,很容易找到 lexer/parser。 (否则你必须自己编写 lexer/parser 或使用类似 lex/yacc 的东西)。
但是,我们必须将文本的每个部分部分反序列化为正确的类型。
我们将使用 Newtonsoft.Json
来执行此操作:
// We define a base class abstract (it cannot be instantiated and we can enforce implementation of methods like the Accept()
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:
// Load text in memory
var text = File.ReadAllText("path/to/my/file.json");
// Get Token instance
var jsonToken = JObject.Parse(text);
- 我们必须处理
JToken
以提取正确的class实例:
// Get the strong typed tree of token
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;
}
如您所见,文本上仍有一些切换模式。
- 然后调用令牌访客:
// Create the visitor
var tokenVisitor = new TokenVisitor();
// Visit the tree with visitor
token.Accept(tokenVisitor);
// Output the result
Console.WriteLine(tokenVisitor.Output);
代码TokenVisitor
internal class TokenVisitor : IVisitor
{
private readonly StringBuilder _builder = new StringBuilder();
// invert the order of children first
private int firstIndex = 1;
private int secondIndex = 0;
// Keep track of name tokens
private readonly HashSet<BaseToken> _visitedTokens = new HashSet<BaseToken>();
public string Output => _builder.ToString();
public void Visit(MethodToken token)
{
// Store local to avoid recursive call;
var localFirst = firstIndex;
var localSecond = secondIndex;
// back to normal order of children
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
上找到完整代码