Реализация шаблона посетителя в 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))
4 ответов
для реализации шаблона посетителя вам нужны два простых интерфейса
-
IVisitable
СAccept
метод сIVisitor
в качестве параметра. -
IVisitor
многоVisit
методы для каждой реализацииIVisitable
таким образом, основная идея шаблона посетителя заключается в динамическом изменении поведения в соответствии с типом реализации.
для вашего случая вещь, которую вы хотите посетить (посещаемый), является Object
класс, который, по-видимому, не имеет разных производных, и вы хотите изменить поведение в соответствии со значением свойства, а не типом. Таким образом, шаблон посетителя-это не то, что вам действительно нужно здесь, и я настоятельно рекомендую вам рассмотреть ответы с помощью рекурсивного метода.
но если вы действительно хотите использовать шаблон посетителя здесь, это может выглядеть примерно так.
interface IVisitable { void Accept(IVisitor visitor); }
interface IVisitor {
void VisitAnd(Object obj);
void VisitEquals(Object obj);
}
С Object
класс-это простой POCO я предполагаю, что вы не захотите реализовать интерфейс и добавить метод в этот класс. Так что вам понадобится адаптер объект, который приспосабливается Object
to 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(")");
}
}
это может быть не то, что вы хотите. Но один из способов создать вывод, который вы хотите без использования шаблона посетителя, - добавить следующий метод в Object
класс, как это:
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");
}
во-первых, у вас неправильный порядок в результате.Во-вторых, что-то вы пропускаете в результате.Финал должен быть:
(((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
}
}
}
}
});
JSON явно представляет дерево токенов (возможно, созданное синтаксическим анализатором).
шаблон посетителя использовать полиморфизм.
для использования шаблоном посетителя необходимо десериализовать его для получения объектов с различным поведением посещения:
- MethodToken
- OperatorToken
- NameToken
тогда IVisitor должен реализовать Метод посещения для каждого:
public class Visitor
{
void Visit(MethodToken token) { /* */ }
void Visit(OperatorToken token) { /* */ }
void Visit(NameToken token) { /* */ }
}