Implementing visitor Pattern in C#
Asked Answered
S

4

8

I'm new in this pattern , could please someone help me in it?

I got an Object like this :

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

Here is an JSON example:

  {
    "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": []
        }]
    }]
}

My question how to make Visitor Pattern in order to get this final string:

(Name IsEqual 3)And((Name IsEqul 5)And(Name IsEqual 6))
Stalag answered 4/10, 2015 at 11:48 Comment(11)
Your question is not clear, please work on it. What do you mean that Name and Value can be a method or operator. And what do you mean by "ex"? How is "And" and "IsEqualTo" related to your question?Ownership
Ok, there is a schema, but what is the question?Corticosterone
Maybe you are looking for something like Expression Trees msdn.microsoft.com/en-us/library/bb397951.aspx ? By the way, Object is not a good class name.Windtight
Updated with a JSON , guess question is more clear nowStalag
Are you sure the JSON example is correct? You have an Operator (IsEqual) that has a child operator.Ownership
Yes sir i get such dynamic json's and was asked to implement this pattern being totally novice in itStalag
Your JSON looks like a specification as in the specification patternNga
What does the following mean? IsEqual(IsEqual(Name , 5))? The first equals has a single child? what does that mean?Ownership
@Yacoub Massad , my bad updated jsonStalag
Do you have to use the Visitor pattern?Ownership
Possible duplicate of Example with Visitor PatternZoometry
A
26

To implement visitor pattern you need two simple interfaces

  1. IVisitable with an Accept method having the IVisitor as the parameter.
  2. IVisitor with many Visit methods for each implementation of IVisitable

So basic idea of the visitor pattern is to change the behavior dynamically according to the type of implementation.

For your case the thing you want to visit (the visitable) is the Object class which apparently does not have different derivatives and you want to change the behavior according to a property value not the type. So Visitor Pattern is not what you really need here and I highly recommend you to consider the answers with the recursive method.

But if you really want to use visitor pattern here, it may look something like this.

interface IVisitable { void Accept(IVisitor visitor); }

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

Since the Object class is a simple POCO I assume you won't want to implement an interface and add a method into this class. So you'll need an adapter object which adapts 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);
    }
}

And finally the visitor implementation may look like this

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(")");
    }
}
Ahoufe answered 4/10, 2015 at 14:16 Comment(2)
Upvote for a great, yet simple example of this pattern. Thanks!Reticent
As @Zoometry stated below. Visitor pattern uses polymorphism. The implementation in this answer does not. It uses if-else instead. This is not visitor design pattern.Tittup
Z
15

The JSON clearly represents a token tree (possibly produced by a parser).

Visitor pattern use polymorphism.

In order to be used by a Visitor pattern, you must deserialize it to obtain objects with the different Visit behavior :

  • MethodToken
  • OperatorToken
  • NameToken

Then IVisitor should implement Visit method for each:

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

Additional remark:

Object is a really poor name especially in C# as Object is the base class for every classes, not to mention the conflict, it doesn't convey any special meaning ... What about token ?

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

About property Childs...

Purpose of Visitor

You shouldn't use a screwdriver if you don't know when/why to use it (by the way it can be dangerous).

Visitor pattern is useful to avoid 'ugly'/hard to maintain/painful to read dozen switch cases or the even worse if else if else while giving you the strong type checking advantage. It also helps to keep related code (high cohesion) in one class (the Visitor). Of course, once implemented, the tree of objects (here tokens) can be visited by several kind of visitors as long as they implement the IVisitor interface.

In your case, you must first convert each Token to a strongly subtype of Token (through Dictionary mapping to avoid any if/switch or custom deserialization)

In your case:

  1. First read the text (obviously it's json format) and transform it to an object. We usually call this deserialization. It's possible here because the text is already formatted with a well-known correct structured format for which it is easy to find a lexer/parser. (Otherwise you would have to write your own lexer/parser or use something like lex/yacc).

However, we have to partial deserialize each part of the text to the right type. We will use Newtonsoft.Json to do this:

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

Read the text and parse Json:

// Load text in memory
var text = File.ReadAllText("path/to/my/file.json");
// Get Token instance
var jsonToken = JObject.Parse(text);
  1. We must process JToken to extract the right class instances:
// Get the strong typed tree of token
var token = CreateToken(jsonToken);

CreateToken method:

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

As you can see there are still some switch pattern on text.

  1. Then call the token visitor:
// Create the visitor
var tokenVisitor = new TokenVisitor();
// Visit the tree with visitor
token.Accept(tokenVisitor);
// Output the result
Console.WriteLine(tokenVisitor.Output);

Code of 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);
        }
    }
}

The above implementation seeks to cope with your expectations(ie output exactly the expected string). It may not be bulletproof. You can find the full code on GitHub

Zoometry answered 23/12, 2015 at 18:43 Comment(2)
Unlike the others, this is Visitor design pattern.Tittup
The best answer is this oneSharleensharlene
O
0

First of all you have wrong order in the result.Second, somethimes you miss brackets in the result.Final it should be:

(((Name IsEqual 5) And (Name IsEqual 6)) And (Name IsEqual 3))

To complete this task you should use recursive function.

  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 "-";
        }

Below is your example in the code:

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
                                   }
                                }
                            }
                        }
            });
Oscular answered 4/10, 2015 at 13:36 Comment(1)
How this reply can answer the question?Clackmannan
O
-1

This might not be what you want. But one way to create the output that you want without using the Visitor pattern is add the following method to the Object class, like this:

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");
}
Ownership answered 4/10, 2015 at 13:27 Comment(1)
The question is about "how implement the visitor pattern"Clackmannan

© 2022 - 2024 — McMap. All rights reserved.