在C#中实现访问者模式

时间:2015-10-04 11:48:06

标签: c# visitor-pattern

我是这种模式的新人,可以请别人帮助我吗?

我有一个像这样的对象:

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 个答案:

答案 0 :(得分:15)

要实现访问者模式,您需要两个简单的接口

  1. IVisitable使用Accept方法,其中IVisitor为参数。
  2. IVisitorVisit
  3. 的每次实施提供了多种IVisitable方法

    访问者模式的基本思想是根据实现的类型动态地改变行为。

    对于您的情况,您要访问的内容(可访问的)是Object类,它显然没有不同的衍生产品,您希望根据属性值而不是类型更改行为。所以访客模式不是你真正需要的,我强烈建议你用递归方法来考虑答案。

    但是如果你真的想在这里使用访客模式,它可能看起来像这样。

    interface IVisitable { void Accept(IVisitor visitor); }
    
    interface IVisitor {
        void VisitAnd(Object obj);
        void VisitEquals(Object obj);
    }
    

    由于Object类是一个简单的POCO,我假设你不想实现一个接口并在这个类中添加一个方法。因此,您需要一个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(")");
        }
    }
    

答案 1 :(得分:5)

JSON清楚地表示一个标记树(可能由解析器生成)。

访客模式使用多态。

为了供访问者模式使用,您必须反序列化它以获取具有不同访问行为的对象:

  • MethodToken
  • OperatorToken
  • NameToken

然后IVisitor应为每个实现访问方法:

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是每个类的基类,更不用说冲突,它没有传达任何特殊含义......什么关于令牌?

public class Token
{
    public string Name { get; set; }
    public object Value { get; set; }
    public List<Token> Childs { get; set; }
}

About property Childs...

访客目的

如果您不知道何时/为何使用螺丝刀,那么您不应该使用螺丝刀(通过它可能是危险的方式)。

访问者模式有助于避免难以维护/难以阅读打开案例或更糟糕if else if else,同时为您提供强大的类型检查优势。它还有助于在一个类(访问者)中保留相关代码(高内聚)。当然,一旦实现,只要实现IVisitor接口,几种访问者就可以访问对象树(此处为令牌)。

在您的情况下,您必须先将每个Token转换为Token的强子类型(通过字典映射以避免任何if / switch或自定义反序列化)

答案 2 :(得分:1)

首先,你的结果中的顺序错误。第二,有些时候你会错过结果中的括号。最后应该是:

(((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
                                   }
                                }
                            }
                        }
            });

答案 3 :(得分:0)

这可能不是你想要的。但是,在不使用访问者模式的情况下创建所需输出的一种方法是将以下方法添加到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");
}