How to work with the Visitor design pattern

The Visitor design pattern provides flexibility in your designs by enabling you to define a new operation without needing to change the types of the elements on which it would operate

visitor design pattern
IDG

The Visitor design pattern is a GOF (Gang of Four) pattern and can be used to define a new operation on your class sans the need of changing the existing code. GOF patterns are classified into creational, behavioral, and structural categories depending on their purpose and usage. The Visitor design pattern is behavioral pattern, and it's a good choice if you would want to perform similar kinds of operations on objects of different types that are grouped inside a structure.

The basic intent of the Visitor design pattern is to isolate an operation from an object structure on which it would operate. It's a good choice when you're attempting to represent recursive structures like directory trees, XML structures, etc. You can use the Visitor Design Pattern to build a data model with limited internal functionality and then have a set of visitors that can operate on the data. In essence, you can leverage this pattern to have a visitor type visit each of the elements of a data structure without the need of having an upfront knowledge on the structure. This design pattern enables you to isolate the data model from the operations to be performed on the data. This isolation helps you to add more and more functionalities (algorithms that can be used to perform operations on the data) over time.

Implementing the Visitor pattern

First off, let's now take a look at the participants in a typical implementation of this design pattern. Here's the list of the participants in the Visitor pattern.

  • Visitor -- This represents a type (either an abstract class or an interface) that provides a declaration on the visit operation to be performed on the classes to be visited.
  • ConcreteVisitor -- This represents a type that implements each of the visit operations that have been declared by the Visitor.
  • Element -- This represents an abstract type that declares the accept operation to be performed and accepts a visitor as a parameter.
  • ConcreteElement -- This represents a type that defines an accept operation. A visitor object is passed as an argument.
  • ObjectStructure -- This represents a type that contains a collection of all the objects that are to be visited.

And now, let's write some code. First off, you'll need two interfaces: the IVisitor and the IVisitable. While the former can have declarations of one or more Visit methods, the latter will have the declaration of an Accept method which in turn will accept a reference to the IVisitor interface as an argument. The following code snippet illustrates the IVisitor interface.

public interface IVisitor

    {

        void Visit(Student student);

        void Visit(Course course);

    }

The IVisitor interface contains the declaration of two Visit methods -- one of them accepts a reference to an instance of a Student as an argument and the other accepts a reference to an instance of Course as an argument.

Both the Student and the Course classes implement the IVisitable interface.

public interface IVisitable

    {

        void Accept(IVisitor visitor);

    }

The IVisitable interface contains the declaration of one method named Accept. This method, in turn, accepts a reference to an instance of type IVisitor. Here's how the Student and the Course classes look like.

public class Student : IVisitable

    {

        public string Name { get; set; }

        public void Accept(IVisitor visitor)

        {

            visitor.Visit(this);

        }

    }

    public class Course : IVisitable

    {

        public string Code { get; set; }

        public void Accept(IVisitor visitor)

        {

            visitor.Visit(this);

        }

    }

And now, here's the code for the concrete visitor class. The MarksVisitor class given next implements the IVisitor interface and contains the implementation of the Visit methods.

public class MarksVisitor : IVisitor

    {

        public void Visit(Student student)

        {

            Console.WriteLine(student.Name + "\t" + student.Grade);

        }

        public void Visit(Course course)

        {

            Console.WriteLine(course.Code);

        }

    }   

Here's how the ObjectStructure class looks like -- I leave it to the readers to change the name of this class appropriately. This class contains the Attach and Detach methods to add or remove elements from a collection of visitable instances.

public class ObjectStructure

    {

        List<IVisitable> lstElements = new List<IVisitable>();

        public void Attach(IVisitable visitable)

        {

            lstElements.Add(visitable);

        }

        public void Detach(IVisitable element)

        {

            lstElements.Remove(element);

        }

        public void Accept(IVisitor visitor)

        {

            foreach (IVisitable v in lstElements)

            {

                v.Accept(visitor);

            }

        }

    }

The following code snippet shows how you can test the entire code from a console application.

static void Main(string[] args)

        {

            Student student = new Student();

            student.Name = "Allie";

            student.Grade = "A";

            Course course = new Course();

            course.Code = "C001";

            MarksVisitor visitor = new MarksVisitor();

            ObjectStructure obj = new ObjectStructure();

            obj.Attach(student);

            obj.Attach(course);

            obj.Accept(visitor);

            Console.ReadKey();

        }

Copyright © 2017 IDG Communications, Inc.

How to choose a low-code development platform