You are here:   ArielOrtiz.com > Software Design and Architecture > Lab 10: Visitor Pattern

Lab 10: Visitor Pattern

Objectives

During this lab session:

This activity helps the student develop the following skills, values and attitudes: ability to analyze and synthesize, capacity for identifying and solving problems, and efficient use of computer systems.

Activity Description

This lab can be developed individually or in pairs.

Make a new folder called visitor. Place all the files you create in this folder.

  1. Read an introduction to the Visitor Pattern at the Wikipedia in order to understand its general concepts.
  2. What follows is one possible generic implementation of the Visitor Pattern using C#. This example basically allows representing trees of arithmetic expressions, similar to those seen in our previous class when we discussed the Interpreter Pattern.

    namespace VisitorPattern {
        
        public interface IVisitor<T> {
            T Visit(Number<T> node);
            T Visit(Plus<T> node);        
            T Visit(Times<T> node);        
        }
        
        public interface INode<T> {
            T Accept(IVisitor<T> visitor);
        }
        
        public class Number<T>: INode<T> {
            public int Value { get; private set; }
            public Number(int value) {
                Value = value;
            }
            public T Accept(IVisitor<T> visitor) {
                return visitor.Visit(this);
            }
        }
        
        public abstract class BinaryOperator<T>: INode<T> {
            public INode<T> Left { get; private set; }
            public INode<T> Right { get; private set; }
            public BinaryOperator(INode<T> left, INode<T> right) {
                Left = left;
                Right = right;
            }
            public abstract T Accept(IVisitor<T> visitor);
        }
        
        public class Plus<T>: BinaryOperator<T> {
            public Plus(INode<T> left, INode<T> right): base(left, right) {}
            public override T Accept(IVisitor<T> visitor) {
                return visitor.Visit(this);
            }        
        }
            
        public class Times<T>: BinaryOperator<T> {
            public Times(INode<T> left, INode<T> right): base(left, right) {}
            public override T Accept(IVisitor<T> visitor) {
                return visitor.Visit(this);
            }        
        }
    }

    A specific visitor can now be implemented like this:

    namespace VisitorPattern {
    
        public class Eval: IVisitor<int> {
            
            public int Visit(Number<int> node) {
                return node.Value;           
            }
            
            public int Visit(Plus<int> node) {
                return node.Left.Accept(this) + node.Right.Accept(this);
            }
            
            public int Visit(Times<int> node) {
                return node.Left.Accept(this) * node.Right.Accept(this);
            }
        }
    }

    Extend the given code in the following ways:

    1. Add two new kinds of nodes: Less<T> (the binary "less than" operator, that returns 1 when true, or 0 when false) and Condition<T> (a ternary conditional operator).
    2. Add two new visitor classes: InOrder and PreOrder. Check the unit tests in order to understand how these classes should work.
  3. Make sure the code that you write behaves exactly as expected by the following unit tests:

    namespace VisitorPattern {
        
        using NUnit.Framework;
        
        [TestFixture]
        public class TestVisitor {
            
            INode<int> intRoot;
            INode<string> strRoot;
                    
            [SetUp]
            public void Init() {
                
                // if 2 + 2 < 2 * 2 then 2 + 3 * 4 else (2 + 3) * 4
                
                intRoot = 
                    new Condition<int>(
                        new Less<int>(
                                new Plus<int>(
                                    new Number<int>(2),
                                    new Number<int>(2)),
                                new Times<int>(
                                    new Number<int>(2),
                                    new Number<int>(2))),
                        new Plus<int>(
                            new Number<int>(2),
                            new Times<int>(
                                new Number<int>(3),
                                new Number<int>(4))),
                        new Times<int>(
                            new Plus<int>(
                                new Number<int>(2),
                                new Number<int>(3)),
                            new Number<int>(4)));
                    
                strRoot = 
                    new Condition<string>(
                        new Less<string>(
                                new Plus<string>(
                                    new Number<string>(2),
                                    new Number<string>(2)),
                                new Times<string>(
                                    new Number<string>(2),
                                    new Number<string>(2))),
                        new Plus<string>(
                            new Number<string>(2),
                            new Times<string>(
                                new Number<string>(3),
                                new Number<string>(4))),
                        new Times<string>(
                            new Plus<string>(
                                new Number<string>(2),
                                new Number<string>(3)),
                            new Number<string>(4)));
            }
            
            [Test]
            public void TestEval() {
                Assert.AreEqual(20, intRoot.Accept(new Eval()));
            }
            
            [Test]
            public void TestInOrder() {
                Assert.AreEqual(
                    "(((2 + 2) < (2 * 2)) ? (2 + (3 * 4)) : ((2 + 3) * 4))", 
                    strRoot.Accept(new InOrder()));
            }
            
            [Test]
            public void TestPreOrder() {
                Assert.AreEqual(
                    "(if (< (+ 2 2) (* 2 2)) (+ 2 (* 3 4)) (* (+ 2 3) 4))", 
                    strRoot.Accept(new PreOrder()));
            }        
        }
    }

Deliverables

To hand in your lab work, follow these instructions:

Evaluation

This activity will be evaluated using the following criteria:

100 The code works as requested.
60-90 The code works, but has some flaws.
20-50 The code doesn't work, but it seams that some amount of time was spent on it.
DA The program was plagiarized.
© 1996-2009 by Ariel Ortiz (ariel.ortiz@itesm.mx)
Made with Django | Licensed under Creative Commons | Valid XHTML | Valid CSS