Visitor Pattern

From eqqon

Revision as of 09:23, 21 November 2007 by Henon (Talk | contribs)
Jump to: navigation, search


Contents

Motivation for applying the Visitor Pattern

Imagine you have a big class hierarchy and you are writing a sophisticated class that does something different for each type in the hierarchy. You cannot put the functionality into the respective classes for some reason. Here is a very simplified code example which demonstrates the situation and implements a naive solution.

Example

public class A {}
public class B : A {}
public class C : B {}

public class Printer
{
    public void Print(A element) { Console.WriteLine("A"); }
    public void Print(B element) { Console.WriteLine("B"); }
    public void Print(C element) { Console.WriteLine("C"); }
    public void Print(A[] array)
    {
        foreach (A element in array)
        {
            // here we reimplement polymorphic method dispatching!
            if (element is C)
                Print(element as C);
            else if (element is B)
                Print(element as B);
            else if (element is A)
                Print(element);
        }
    }
}

As you can imagine this naive implementation would become very complicated for a larger and deeper class hierarchy. It would be error prone and unmaintainable. Every time you add a new class to your hierarchy you would need to extend the dispatching logic in the Printer class.

Applying the Visitor Pattern

The complete example program

using System;
using System.Collections.Generic;
using System.Text;

namespace CodeSnippet
{

    public class A
    {
        public virtual void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(A element)
    }

    public class B : A
    {
        public override void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(B element)
    }

    public class C : B
    {
        public override void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(C element)
    }

    namespace NaiveApproach
    {
        public class Printer
        {
            public void Print(A element) { Console.WriteLine("A"); }

            public void Print(B element) { Console.WriteLine("B"); }

            public void Print(C element) { Console.WriteLine("C"); }

            public void Print(A[] array)
            {
                foreach (A element in array)
                {
                    // here we reimplement polymorphic method dispatching!
                    if (element is C)
                        Print(element as C);
                    else if (element is B)
                        Print(element as B);
                    else if (element is A)
                        Print(element);
                }
            }
        }
    }

    namespace VisitorPattern
    {
        public class Printer
        {
            public void Print(A element) { Console.WriteLine("A"); }

            public void Print(B element) { Console.WriteLine("B"); }

            public void Print(C element) { Console.WriteLine("C"); }

            public void Print(A[] array)
            {
                foreach (A element in array)
                {
                    // here we make use of the language's polymorphic method dispatching
                    element.Print(this);
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            A[] array = { new A(), new B(), new C() };
            Console.WriteLine("Naive Approach:");
            new NaiveApproach.Printer().Print(array);
            Console.WriteLine("Visitor Pattern:");
            new VisitorPattern.Printer().Print(array);
            Console.ReadLine();
        }
    }
}