Visitor Design Pattern

Visitor design pattern allows you to add new behaviors to an existing object without changing the object structure.

Visitor pattern separates the non-related behaviors from the object and put them into a separate object. Visitor pattern creates a separate object for each new functionality.

This pattern supports both single responsibility and open/closed SOLID principles. By implementing this pattern, your object can delegate responsibilities to different objects. For e.g. you have an Employee object which has responsibility of CRUD operations. But you have to add another responsibility to an Employee object to generate reports. By using this visitor pattern, you can add behaviors for generating report without modifying the Employee object. Your Employee object also follow the open/closed principle. As you don’t have to modify Employee object when you have to add new behavior.

Structure

Visitor-Pattern

For implementing this pattern, you have to create two interfaces. IElement and IVisitor.

IElement interface has only a single method Visit which takes the parameter of IVisitor interface.

IVisitor interface has methods for every class which implements the IElement interface. IElement interface is implemented by class A and class B. Then for both the classes, we have a single method in IVisitor class. Accept(ClassA) and Accept(ClassB).

For introducing new behaviors to existing Elements objects, we will create a separate class for each behavior which implements the IVisitor interface. In the above diagram, ConcreteVisitor1 and Concretevisitor2 implements the IVisitor interface.

Each derive class of IElement implements the Visit(IVisitor visitor) method. Visit method implementation calls the visitor.Accept method using the this parameter. If ClassA call the Accept method then in ConcreteVisitor Accept(ClassA) method is called and in the Accept method we can do our processing on ClassA object.

Visitor Design Pattern Example

Visitor-Example

Car and Bike classes implements the IStore interface and we have two visitor classes, PriceVisitor and WeightVisitor.

Below is the implementation of above diagram in C#.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DesignPatterns_Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<IStore> store = new List<IStore>();
            store.Add(new Car() { CarName = "A1", Price = 200M, CarType = "Mercedes" });
            store.Add(new Car() { CarName = "A2", Price = 100M, CarType = "Normal" });

            store.Add(new Bike() { BikeName = "B1", Price = 50M, BikeType = "Bullet" });
            store.Add(new Bike() { BikeName = "B2", Price = 30M, BikeType = "Normal" });

            //Show price of each item
            PriceVisitor priceVisitor = new PriceVisitor();
            foreach (var element in store)
            {
                element.Visit(priceVisitor);
            }

            //Show weight of each item
            WeightVisitor weightVisitor = new WeightVisitor();
            foreach (var element in store)
            {
                element.Visit(weightVisitor);
            }

            Console.ReadLine();
        }
    }

    public interface IStore
    {
        void Visit(IVisitor visitor);
    }

    public interface IVisitor
    {
        void Accept(Car car);
        void Accept(Bike bike);
    }

    public class Car : IStore
    {
        public string CarName { get; set; }
        public decimal Price { get; set; }
        public string CarType { get; set; }

        public void Visit(IVisitor visitor)
        {
            visitor.Accept(this);
        }
    }

    public class Bike : IStore
    {
        public string BikeName { get; set; }
        public decimal Price { get; set; }
        public string BikeType { get; set; }

        public void Visit(IVisitor visitor)
        {
            visitor.Accept(this);
        }
    }

    public class PriceVisitor : IVisitor
    {
        private const int CAR_DISCOUNT = 5;
        private const int BIKE_DISCOUNT = 2;

        public void Accept(Car car)
        {
            decimal carPriceAfterDicount = car.Price - ((car.Price / 100) * CAR_DISCOUNT);
            Console.WriteLine("Car {0} price: {1}", car.CarName, carPriceAfterDicount);
        }

        public void Accept(Bike bike)
        {
            decimal bikePriceAfterDicount = bike.Price - ((bike.Price / 100) * BIKE_DISCOUNT);
            Console.WriteLine("Bike {0} price: {1}", bike.BikeName, bikePriceAfterDicount);
        }
    }

    public class WeightVisitor : IVisitor
    {
        public void Accept(Car car)
        {
            switch (car.CarType)
            {
                case "Mercedes":
                    Console.WriteLine("Car {0} weight: {1} KG", car.CarName, 1750);
                    break;
                case "Normal":
                    Console.WriteLine("Car {0} weight: {1} KG", car.CarName, 750);
                    break;
            }
        }

        public void Accept(Bike bike)
        {
            switch (bike.BikeType)
            {
                case "Bullet":
                    Console.WriteLine("Bike {0} weight: {1} KG", bike.BikeName, 300);
                    break;
                case "Normal":
                    Console.WriteLine("Bike {0} weight: {1} KG", bike.BikeName, 100);
                    break;
            }
        }
    }
}

//RESULT
//Car A1 price: 190
//Car A2 price: 95
//Bike B1 price: 49.0
//Bike B2 price: 29.4

//Car A1 weight: 1750 KG
//Car A2 weight: 750 KG
//Bike B1 weight: 300 KG
//Bike B2 weight: 100 KG