Реализация шаблона посетителя в 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 ответов


для реализации шаблона посетителя вам нужны два простых интерфейса

  1. IVisitable С Accept метод с IVisitor в качестве параметра.
  2. 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) { /* */ }
}