Real-Time Examples of Visitor Design Pattern in C#

Real-Time Examples of Visitor Design Patterns in C#

In this article, I will discuss the Real-Time Examples of the Visitor Design Pattern in C#. Please read our previous article discussing the basic concepts of Visitor Design Patterns in C# with Examples. At the end of this article, you will understand the following Real-time Examples using the Visitor Design Pattern in C#.

  1. Computer Parts Store
  2. Postal Service System
  3. Online Shopping System
  4. Zoo Simulation
  5. Computer System Component Hierarchy
  6. Tax Calculation System
  7. Ticket Pricing System
  8. Post and Package Delivery System
Real-Time Example of Visitor Design Pattern in C#: Computer Parts Store

Imagine you have various computer parts and want to compute the total price for these parts separately. You might want to list down the part names. Instead of adding these methods directly into each part’s class, you can utilize the Visitor pattern. Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace VisitorDesignPattern
{
    // Element Interface
    interface IComputerPart
    {
        void Accept(IComputerPartVisitor computerPartVisitor);
    }

    // Concrete Elements
    class Keyboard : IComputerPart
    {
        public void Accept(IComputerPartVisitor computerPartVisitor)
        {
            computerPartVisitor.Visit(this);
        }
    }

    class Monitor : IComputerPart
    {
        public void Accept(IComputerPartVisitor computerPartVisitor)
        {
            computerPartVisitor.Visit(this);
        }
    }

    class Mouse : IComputerPart
    {
        public void Accept(IComputerPartVisitor computerPartVisitor)
        {
            computerPartVisitor.Visit(this);
        }
    }

    // Visitor Interface
    interface IComputerPartVisitor
    {
        void Visit(Mouse mouse);
        void Visit(Keyboard keyboard);
        void Visit(Monitor monitor);
    }

    // Concrete Visitor to compute the price
    class PriceVisitor : IComputerPartVisitor
    {
        private double totalPrice = 0;

        public double TotalPrice => totalPrice;

        public void Visit(Mouse mouse)
        {
            totalPrice += 25; // Example price for Mouse
        }

        public void Visit(Keyboard keyboard)
        {
            totalPrice += 50; // Example price for Keyboard
        }

        public void Visit(Monitor monitor)
        {
            totalPrice += 100; // Example price for Monitor
        }
    }

    // Concrete Visitor to list down part names
    class NameVisitor : IComputerPartVisitor
    {
        private List<string> names = new List<string>();

        public IEnumerable<string> Names => names;

        public void Visit(Mouse mouse)
        {
            names.Add("Mouse");
        }

        public void Visit(Keyboard keyboard)
        {
            names.Add("Keyboard");
        }

        public void Visit(Monitor monitor)
        {
            names.Add("Monitor");
        }
    }
    
    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            var computerParts = new List<IComputerPart>
            {
                new Mouse(),
                new Keyboard(),
                new Monitor()
            };

            var priceVisitor = new PriceVisitor();
            var nameVisitor = new NameVisitor();

            foreach (var part in computerParts)
            {
                part.Accept(priceVisitor);
                part.Accept(nameVisitor);
            }

            Console.WriteLine($"Total Price: {priceVisitor.TotalPrice}");
            Console.WriteLine($"Part Names: {string.Join(", ", nameVisitor.Names)}");

            Console.ReadKey();
        }
    }
}

In this example:

  • We have a hierarchy of computer parts (Mouse, Keyboard, and Monitor), each implementing IComputerPart.
  • The PriceVisitor computes the total price of the computer parts.
  • The NameVisitor lists down the names of the computer parts.
  • The Client class shows how to apply different visitors to the same elements.

This creates a clean separation between the data (computer parts) and operations (computing price, listing names). As new operations are needed (e.g., computing weight, checking warranty), new visitors can be created without altering the existing structure of computer parts. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Computer Parts Store

Real-Time Example of Visitor Design Pattern in C#: Postal Service System

Let’s illustrate the Visitor pattern with the postal service system that handles various mail packages. You might want to determine the postage cost for different types of packages based on weight and dimensions. Later, you might want to introduce functionality to display customs instructions for international shipments. The Visitor pattern allows you to easily add these varied operations without modifying the package classes. Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    interface IMailPackage
    {
        void Accept(IPackageVisitor packageVisitor);
    }

    // Concrete Elements
    class Letter : IMailPackage
    {
        public double Weight { get; } = 0.05;

        public void Accept(IPackageVisitor packageVisitor)
        {
            packageVisitor.Visit(this);
        }
    }

    class Parcel : IMailPackage
    {
        public double Weight { get; } = 1.5;

        public void Accept(IPackageVisitor packageVisitor)
        {
            packageVisitor.Visit(this);
        }
    }

    class InternationalPackage : IMailPackage
    {
        public double Weight { get; } = 3.0;

        public void Accept(IPackageVisitor packageVisitor)
        {
            packageVisitor.Visit(this);
        }
    }

    // Visitor Interface
    interface IPackageVisitor
    {
        void Visit(Letter letter);
        void Visit(Parcel parcel);
        void Visit(InternationalPackage internationalPackage);
    }

    // Concrete Visitor to compute postage cost
    class PostageVisitor : IPackageVisitor
    {
        public double TotalPostage { get; private set; } = 0;

        public void Visit(Letter letter)
        {
            TotalPostage += letter.Weight * 0.5; // Example rate
        }

        public void Visit(Parcel parcel)
        {
            TotalPostage += parcel.Weight * 2; // Example rate
        }

        public void Visit(InternationalPackage internationalPackage)
        {
            TotalPostage += internationalPackage.Weight * 5; // Higher rate for international packages
        }
    }

    // Concrete Visitor to show customs instructions
    class CustomsVisitor : IPackageVisitor
    {
        public void Visit(Letter letter)
        {
            Console.WriteLine("No customs instructions for letters.");
        }

        public void Visit(Parcel parcel)
        {
            Console.WriteLine("Regular customs check for parcels.");
        }

        public void Visit(InternationalPackage internationalPackage)
        {
            Console.WriteLine("Special customs instructions for international packages.");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            var packages = new IMailPackage[]
            {
            new Letter(),
            new Parcel(),
            new InternationalPackage()
            };

            var postageVisitor = new PostageVisitor();
            var customsVisitor = new CustomsVisitor();

            foreach (var package in packages)
            {
                package.Accept(postageVisitor);
                package.Accept(customsVisitor);
            }

            Console.WriteLine($"Total Postage Cost: {postageVisitor.TotalPostage}");
            Console.ReadKey();
        }
    }
}

In this example:

  • We have a hierarchy of mail packages (Letter, Parcel, and InternationalPackage), each with different weights.
  • The PostageVisitor computes the postage costs based on package weight and type.
  • The CustomsVisitor provides customs instructions for each type of package.
  • In the Client class, we show how to use different visitors on the same set of packages.

The benefit of using the Visitor pattern here is the separation of operations (calculating postage, providing customs instructions) from the actual mail packages. A new visitor can be created without modifying the existing package classes if the postal service introduces new operations (like special handling instructions). When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Postal Service System

Real-Time Example of Visitor Design Pattern in C#: Online Shopping System

Let’s consider another real-world scenario: an online shopping system with various products, and you want to provide different kinds of operations for them, like discount calculations and description display. Let’s see how the Visitor design pattern can be applied to this scenario.

Products (Elements):
  • Book
  • Electronic (e.g., Laptop, Phone)
  • Grocery
Visitors:
  • DiscountVisitor (calculates discount)
  • DescriptionVisitor (displays product description)

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    interface IProduct
    {
        void Accept(IProductVisitor visitor);
    }

    // Concrete Elements
    class Book : IProduct
    {
        public double Price { get; set; }
        public string ISBN { get; set; }
        public string Title { get; set; }

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

    class Electronic : IProduct
    {
        public double Price { get; set; }
        public string Brand { get; set; }
        public string Model { get; set; }

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

    class Grocery : IProduct
    {
        public double Price { get; set; }
        public string Name { get; set; }
        public DateTime ExpiryDate { get; set; }

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

    // Visitor Interface
    interface IProductVisitor
    {
        void Visit(Book book);
        void Visit(Electronic electronic);
        void Visit(Grocery grocery);
    }

    // Concrete Visitor for calculating discounts
    class DiscountVisitor : IProductVisitor
    {
        public double DiscountedPrice { get; private set; } = 0;

        public void Visit(Book book)
        {
            DiscountedPrice = book.Price * 0.90;  // 10% discount for books
        }

        public void Visit(Electronic electronic)
        {
            DiscountedPrice = electronic.Price * 0.95;  // 5% discount for electronics
        }

        public void Visit(Grocery grocery)
        {
            DiscountedPrice = grocery.Price;  // No discount for groceries
        }
    }

    // Concrete Visitor for displaying descriptions
    class DescriptionVisitor : IProductVisitor
    {
        public void Visit(Book book)
        {
            Console.WriteLine($"Book: {book.Title}, ISBN: {book.ISBN}");
        }

        public void Visit(Electronic electronic)
        {
            Console.WriteLine($"Electronic: {electronic.Brand} {electronic.Model}");
        }

        public void Visit(Grocery grocery)
        {
            Console.WriteLine($"Grocery: {grocery.Name}, Expires on: {grocery.ExpiryDate.ToShortDateString()}");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IProduct book = new Book { Price = 100, ISBN = "123456", Title = "Design Patterns" };
            IProduct laptop = new Electronic { Price = 1000, Brand = "Dell", Model = "XPS 15" };
            IProduct apple = new Grocery { Price = 2, Name = "Apple", ExpiryDate = DateTime.Now.AddDays(10) };

            var discountVisitor = new DiscountVisitor();
            var descriptionVisitor = new DescriptionVisitor();

            book.Accept(discountVisitor);
            Console.WriteLine($"Discounted Price of Book: ${discountVisitor.DiscountedPrice}");
            book.Accept(descriptionVisitor);

            laptop.Accept(discountVisitor);
            Console.WriteLine($"Discounted Price of Laptop: ${discountVisitor.DiscountedPrice}");
            laptop.Accept(descriptionVisitor);

            apple.Accept(discountVisitor);
            Console.WriteLine($"Discounted Price of Apple: ${discountVisitor.DiscountedPrice}");
            apple.Accept(descriptionVisitor);

            Console.ReadKey();
        }
    }
}

In this example:

  • We have different product types as our Concrete Elements.
  • The DiscountVisitor provides a mechanism to calculate the discount for each product type.
  • The DescriptionVisitor provides a mechanism to display a description for each product type.

This design provides a flexible way to add new operations to the products without altering the product classes. For instance, if you later want to introduce an operation to calculate shipping costs based on product type, you can introduce a new ShippingCostVisitor without modifying the existing product classes. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Online Shopping System

Real-Time Example of Visitor Design Pattern in C#: Zoo Simulation

Let’s consider a zoo simulation where different animals behave differently when a person visits them. In this case, the animals can be our “Elements,” and different types of people (like a Veterinarians, a Caretaker, or a Visitor) can be our “Visitors”.

Animals (Elements):
  • Lion
  • Dolphin
  • Bird
People (Visitors):
  • Veterinarian (checks the health of animals)
  • Caretaker (feeds the animals)
  • ZooVisitor (observes the animals)

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    interface IAnimal
    {
        void Accept(IVisitor visitor);
    }

    // Concrete Elements
    class Lion : IAnimal
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class Dolphin : IAnimal
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class Bird : IAnimal
    {
        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // Visitor Interface
    interface IVisitor
    {
        void Visit(Lion lion);
        void Visit(Dolphin dolphin);
        void Visit(Bird bird);
    }

    // Concrete Visitors
    class Veterinarian : IVisitor
    {
        public void Visit(Lion lion)
        {
            Console.WriteLine("Examines the lion's health.");
        }

        public void Visit(Dolphin dolphin)
        {
            Console.WriteLine("Checks the dolphin's breathing.");
        }

        public void Visit(Bird bird)
        {
            Console.WriteLine("Inspects the bird's wings.");
        }
    }

    class Caretaker : IVisitor
    {
        public void Visit(Lion lion)
        {
            Console.WriteLine("Feeds the lion some meat.");
        }

        public void Visit(Dolphin dolphin)
        {
            Console.WriteLine("Feeds the dolphin some fish.");
        }

        public void Visit(Bird bird)
        {
            Console.WriteLine("Gives seeds to the bird.");
        }
    }

    class ZooVisitor : IVisitor
    {
        public void Visit(Lion lion)
        {
            Console.WriteLine("Watches the lion from a distance.");
        }

        public void Visit(Dolphin dolphin)
        {
            Console.WriteLine("Observes the dolphin playing.");
        }

        public void Visit(Bird bird)
        {
            Console.WriteLine("Listens to the bird's song.");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Program
    {
        public static void Main()
        {
            IAnimal lion = new Lion();
            IAnimal dolphin = new Dolphin();
            IAnimal bird = new Bird();

            IVisitor vet = new Veterinarian();
            IVisitor caretaker = new Caretaker();
            IVisitor visitor = new ZooVisitor();

            Console.WriteLine("Veterinarian visits:");
            lion.Accept(vet);
            dolphin.Accept(vet);
            bird.Accept(vet);

            Console.WriteLine("\nCaretaker visits:");
            lion.Accept(caretaker);
            dolphin.Accept(caretaker);
            bird.Accept(caretaker);

            Console.WriteLine("\nZoo visitor visits:");
            lion.Accept(visitor);
            dolphin.Accept(visitor);
            bird.Accept(visitor);

            Console.ReadKey();
        }
    }
}

In this example:

  • We have different animals in the zoo as Concrete Elements.
  • The different types of people interacting with the animals in varied ways serve as our Visitors.

This design makes it very easy to introduce a new role/person (like a Trainer) without modifying the existing animal classes. Likewise, if the zoo adds a new animal, only the visitors need to be updated, providing a clear separation of concerns. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Zoo Simulation

Real-Time Example of Visitor Design Pattern in C#: Computer System Component Hierarchy

Let’s look at the Visitor Design Pattern in a Computer System Component Hierarchy context. In this example, consider a computer system as an assembly of components such as a Monitor, Keyboard, and CPU. Over time, we may want to perform various operations on these components, like checking the status or performing a diagnostic. We can use the Visitor pattern instead of directly adding these methods to the components (which would violate the open-closed principle).

Components (Elements):
  • Monitor
  • Keyboard
  • CPU
Operations (Visitors):
  • Status Operation: Check the status of a component.
  • Diagnostic Operation: Run diagnostics on a component.

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    public interface IComputerComponent
    {
        void Accept(IComputerVisitor visitor);
    }

    // Concrete Elements
    public class Monitor : IComputerComponent
    {
        public void Accept(IComputerVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    public class Keyboard : IComputerComponent
    {
        public void Accept(IComputerVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    public class CPU : IComputerComponent
    {
        public void Accept(IComputerVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    // Visitor Interface
    public interface IComputerVisitor
    {
        void Visit(Monitor monitor);
        void Visit(Keyboard keyboard);
        void Visit(CPU cpu);
    }

    // Concrete Visitor for Status
    public class StatusVisitor : IComputerVisitor
    {
        public void Visit(Monitor monitor)
        {
            Console.WriteLine("Checking the monitor's status...");
        }

        public void Visit(Keyboard keyboard)
        {
            Console.WriteLine("Checking the keyboard's status...");
        }

        public void Visit(CPU cpu)
        {
            Console.WriteLine("Checking the CPU's status...");
        }
    }

    // Concrete Visitor for Diagnostics
    public class DiagnosticVisitor : IComputerVisitor
    {
        public void Visit(Monitor monitor)
        {
            Console.WriteLine("Running diagnostics on the monitor...");
        }

        public void Visit(Keyboard keyboard)
        {
            Console.WriteLine("Running diagnostics on the keyboard...");
        }

        public void Visit(CPU cpu)
        {
            Console.WriteLine("Running diagnostics on the CPU...");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IComputerComponent[] components = new IComputerComponent[] {
                new Monitor(),
                new Keyboard(),
                new CPU()
            };

            IComputerVisitor statusVisitor = new StatusVisitor();
            IComputerVisitor diagnosticVisitor = new DiagnosticVisitor();

            Console.WriteLine("=== Status Operation ===");
            foreach (var component in components)
            {
                component.Accept(statusVisitor);
            }

            Console.WriteLine("\n=== Diagnostic Operation ===");
            foreach (var component in components)
            {
                component.Accept(diagnosticVisitor);
            }

            Console.ReadKey();
        }
    }
}

In this example:

  • The computer components are the Elements.
  • The operations to check status and diagnostics are the Visitors.

Using the Visitor design pattern, we can easily add new operations (like an update or repair operation) without changing the existing computer component classes. If we ever add a new component, we’d only need to update the visitor interfaces and their implementations. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Computer System Component Hierarchy

Real-Time Example of Visitor Design Pattern in C#: Tax Calculation System

Consider a company with different types of employees, such as Full-time, Part-time, and Interns. Each type of employee has a different method of tax calculation based on their earnings and benefits. Instead of each employee determining their own tax, we can have visitors compute the tax based on their type.

Employees (Elements):
  • FullTimeEmployee
  • PartTimeEmployee
  • Intern
Operations (Visitors):
  • IncomeTaxCalculator
  • YearEndBonusCalculator

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    public interface IEmployee
    {
        decimal Salary { get; set; }
        void Accept(IEmployeeVisitor visitor);
    }

    // Concrete Elements
    public class FullTimeEmployee : IEmployee
    {
        public decimal Salary { get; set; }

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

    public class PartTimeEmployee : IEmployee
    {
        public decimal Salary { get; set; }

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

    public class Intern : IEmployee
    {
        public decimal Salary { get; set; }

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

    // Visitor Interface
    public interface IEmployeeVisitor
    {
        void Visit(FullTimeEmployee fullTimeEmployee);
        void Visit(PartTimeEmployee partTimeEmployee);
        void Visit(Intern intern);
    }

    // Concrete Visitor for Income Tax Calculation
    public class IncomeTaxCalculator : IEmployeeVisitor
    {
        public void Visit(FullTimeEmployee fullTimeEmployee)
        {
            Console.WriteLine($"Income Tax for FullTimeEmployee: ${fullTimeEmployee.Salary * 0.30m}");
        }

        public void Visit(PartTimeEmployee partTimeEmployee)
        {
            Console.WriteLine($"Income Tax for PartTimeEmployee: ${partTimeEmployee.Salary * 0.20m}");
        }

        public void Visit(Intern intern)
        {
            Console.WriteLine($"Income Tax for Intern: ${0}");  // No tax for interns
        }
    }

    // Concrete Visitor for Year End Bonus Calculation
    public class YearEndBonusCalculator : IEmployeeVisitor
    {
        public void Visit(FullTimeEmployee fullTimeEmployee)
        {
            Console.WriteLine($"Year End Bonus for FullTimeEmployee: ${fullTimeEmployee.Salary * 0.10m}");
        }

        public void Visit(PartTimeEmployee partTimeEmployee)
        {
            Console.WriteLine($"Year End Bonus for PartTimeEmployee: ${partTimeEmployee.Salary * 0.05m}");
        }

        public void Visit(Intern intern)
        {
            Console.WriteLine($"Year End Bonus for Intern: ${500}");  // Flat bonus for interns
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IEmployee[] employees = new IEmployee[] {
                new FullTimeEmployee { Salary = 60000 },
                new PartTimeEmployee { Salary = 30000 },
                new Intern { Salary = 10000 }
            };

            IEmployeeVisitor taxCalculator = new IncomeTaxCalculator();
            IEmployeeVisitor bonusCalculator = new YearEndBonusCalculator();

            Console.WriteLine("=== Income Tax Calculation ===");
            foreach (var employee in employees)
            {
                employee.Accept(taxCalculator);
            }

            Console.WriteLine("\n=== Year End Bonus Calculation ===");
            foreach (var employee in employees)
            {
                employee.Accept(bonusCalculator);
            }

            Console.ReadKey();
        }
    }
}

This example illustrates:

  • Employees as Elements
  • Different financial calculations (like tax and bonus calculations) as Visitors

The advantage of using the Visitor design pattern here is that if we want to add a new financial operation (like health benefits calculation), we create a new visitor without altering the existing employee classes. Similarly, if we introduce a new type of employee, only the visitors need to be modified. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Tax Calculation System

Real-Time Example of Visitor Design Pattern in C#: Ticket Pricing System

Imagine a theme park with different types of visitors, and the ticket prices vary based on the type of visitor and the kind of ticket they opt for.

Theme Park Visitors (Elements):
  • Adult
  • Child
  • Senior
Ticket Types (Visitors):
  • Standard Ticket
  • VIP Ticket
  • Holiday Special Ticket

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    public interface IVisitor
    {
        void Accept(ITicketVisitor ticketVisitor);
    }

    // Concrete Elements
    public class Adult : IVisitor
    {
        public void Accept(ITicketVisitor ticketVisitor)
        {
            ticketVisitor.AssignTicketPrice(this);
        }
    }

    public class Child : IVisitor
    {
        public void Accept(ITicketVisitor ticketVisitor)
        {
            ticketVisitor.AssignTicketPrice(this);
        }
    }

    public class Senior : IVisitor
    {
        public void Accept(ITicketVisitor ticketVisitor)
        {
            ticketVisitor.AssignTicketPrice(this);
        }
    }

    // Visitor Interface
    public interface ITicketVisitor
    {
        void AssignTicketPrice(Adult adult);
        void AssignTicketPrice(Child child);
        void AssignTicketPrice(Senior senior);
    }

    // Concrete Visitor for Standard Ticket
    public class StandardTicket : ITicketVisitor
    {
        public void AssignTicketPrice(Adult adult)
        {
            Console.WriteLine("Standard Ticket Price for Adult: $50");
        }

        public void AssignTicketPrice(Child child)
        {
            Console.WriteLine("Standard Ticket Price for Child: $25");
        }

        public void AssignTicketPrice(Senior senior)
        {
            Console.WriteLine("Standard Ticket Price for Senior: $40");
        }
    }

    // Concrete Visitor for VIP Ticket
    public class VIPTicket : ITicketVisitor
    {
        public void AssignTicketPrice(Adult adult)
        {
            Console.WriteLine("VIP Ticket Price for Adult: $80");
        }

        public void AssignTicketPrice(Child child)
        {
            Console.WriteLine("VIP Ticket Price for Child: $50");
        }

        public void AssignTicketPrice(Senior senior)
        {
            Console.WriteLine("VIP Ticket Price for Senior: $70");
        }
    }

    // Concrete Visitor for Holiday Special Ticket
    public class HolidaySpecialTicket : ITicketVisitor
    {
        public void AssignTicketPrice(Adult adult)
        {
            Console.WriteLine("Holiday Special Ticket Price for Adult: $60");
        }

        public void AssignTicketPrice(Child child)
        {
            Console.WriteLine("Holiday Special Ticket Price for Child: $30");
        }

        public void AssignTicketPrice(Senior senior)
        {
            Console.WriteLine("Holiday Special Ticket Price for Senior: $50");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IVisitor[] parkVisitors = new IVisitor[] {
                new Adult(),
                new Child(),
                new Senior()
            };

            ITicketVisitor standardTicket = new StandardTicket();
            ITicketVisitor vipTicket = new VIPTicket();
            ITicketVisitor holidayTicket = new HolidaySpecialTicket();

            Console.WriteLine("=== Standard Ticket Prices ===");
            foreach (var visitor in parkVisitors)
            {
                visitor.Accept(standardTicket);
            }

            Console.WriteLine("\n=== VIP Ticket Prices ===");
            foreach (var visitor in parkVisitors)
            {
                visitor.Accept(vipTicket);
            }

            Console.WriteLine("\n=== Holiday Special Ticket Prices ===");
            foreach (var visitor in parkVisitors)
            {
                visitor.Accept(holidayTicket);
            }

            Console.ReadKey();
        }
    }
}

In this example:

  • Theme park visitors (Adult, Child, Senior) are the Elements.
  • Different types of tickets (Standard, VIP, Holiday Special) act as Visitors.

Using the Visitor design pattern allows for easily adding new ticket types or promotions without changing the existing visitor types. Similarly, if a new type of visitor is introduced, only the ticket visitors must be modified, maintaining a clear separation of concerns. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Ticket Pricing System

Real-Time Example of Visitor Design Pattern in C#: Post and Package Delivery System

In this example, different types of deliveries might need different operations like customs checking, weight verification, or packaging.

Deliveries (Elements):
  • Letter
  • Parcel
  • HeavyDuty
Operations (Visitors):
  • Customs Check
  • Weight Verification
  • Packaging

Let us see how we can implement the above example using the Visitor Design Pattern in C#:

using System;
namespace VisitorDesignPattern
{
    // Element Interface
    public interface IDelivery
    {
        void Accept(IDeliveryOperation operation);
    }

    // Concrete Elements
    public class Letter : IDelivery
    {
        public void Accept(IDeliveryOperation operation)
        {
            operation.PerformOperation(this);
        }
    }

    public class Parcel : IDelivery
    {
        public void Accept(IDeliveryOperation operation)
        {
            operation.PerformOperation(this);
        }
    }

    public class HeavyDuty : IDelivery
    {
        public void Accept(IDeliveryOperation operation)
        {
            operation.PerformOperation(this);
        }
    }

    // Visitor Interface
    public interface IDeliveryOperation
    {
        void PerformOperation(Letter letter);
        void PerformOperation(Parcel parcel);
        void PerformOperation(HeavyDuty heavyDuty);
    }

    // Concrete Visitor for Customs Check
    public class CustomsCheck : IDeliveryOperation
    {
        public void PerformOperation(Letter letter)
        {
            Console.WriteLine("Customs check on a letter. Quick glance.");
        }

        public void PerformOperation(Parcel parcel)
        {
            Console.WriteLine("Customs check on a parcel. More detailed.");
        }

        public void PerformOperation(HeavyDuty heavyDuty)
        {
            Console.WriteLine("Customs check on heavy-duty. Intensive check.");
        }
    }

    // Concrete Visitor for Weight Verification
    public class WeightVerification : IDeliveryOperation
    {
        public void PerformOperation(Letter letter)
        {
            Console.WriteLine("Verified weight for a letter. Lightweight!");
        }

        public void PerformOperation(Parcel parcel)
        {
            Console.WriteLine("Verified weight for a parcel.");
        }

        public void PerformOperation(HeavyDuty heavyDuty)
        {
            Console.WriteLine("Verified weight for heavy-duty. It's quite heavy!");
        }
    }

    // Concrete Visitor for Packaging
    public class Packaging : IDeliveryOperation
    {
        public void PerformOperation(Letter letter)
        {
            Console.WriteLine("Packaging a letter in an envelope.");
        }

        public void PerformOperation(Parcel parcel)
        {
            Console.WriteLine("Packaging a parcel in a box.");
        }

        public void PerformOperation(HeavyDuty heavyDuty)
        {
            Console.WriteLine("Packaging heavy-duty with extra care and layers.");
        }
    }

    // Testing the Visitor Design Pattern
    // Client Code
    public class Client
    {
        public static void Main()
        {
            IDelivery[] deliveries = new IDelivery[] {
            new Letter(),
            new Parcel(),
            new HeavyDuty()
        };

            IDeliveryOperation customsCheck = new CustomsCheck();
            IDeliveryOperation weightVerification = new WeightVerification();
            IDeliveryOperation packaging = new Packaging();

            Console.WriteLine("=== Customs Check ===");
            foreach (var delivery in deliveries)
            {
                delivery.Accept(customsCheck);
            }

            Console.WriteLine("\n=== Weight Verification ===");
            foreach (var delivery in deliveries)
            {
                delivery.Accept(weightVerification);
            }

            Console.WriteLine("\n=== Packaging ===");
            foreach (var delivery in deliveries)
            {
                delivery.Accept(packaging);
            }

            Console.ReadKey();
        }
    }
}

In this example:

  • Various deliveries (Letter, Parcel, HeavyDuty) are the Elements.
  • Different operations (Customs Check, Weight Verification, Packaging) are the Visitors.

Using the Visitor design pattern, we can add new delivery types or operations without modifying existing classes extensively. This ensures a clean separation of concerns, making the system more maintainable and extendable. When you run the above code, you will get the following output.

Real-Time Example of Visitor Design Pattern in C#: Post and Package Delivery System

When to use Visitor Design Pattern in C#?

The Visitor Design Pattern is particularly useful in certain scenarios. You might consider using the Visitor pattern in the following cases:

  • Operations across Diverse Classes: If you have several different classes and need to perform operations that are specific to each of those classes, but you don’t want to add these operations directly to the classes. This is particularly useful when those operations are not tightly related to the primary responsibility of those classes.
  • Separation of Concerns: If you want to separate an algorithm or operation from the classes on which it operates, the Visitor pattern can be useful. This can lead to a cleaner code by segregating responsibilities.
  • Adding Operations without Modifying Classes: When you need to add new operations without modifying the classes, especially if they are part of a closed library for modification. This way, the Visitor pattern allows you to maintain the open/closed principle.
  • Accumulating State: If you need to accumulate state as you visit each element in a structure. Visitors can maintain their state during the traversal process, allowing them to gather and accumulate data as they visit each element.
  • Unified Access across Composite Structures: In conjunction with the Composite pattern, the Visitor pattern can provide a way to perform operations over structures composed of disparate objects, allowing unified access to the composite elements.
  • When you need to break dependency cycles: Visitors can be used to break dependency cycles by decoupling operations from the data structures they operate upon.

However, there are also drawbacks and challenges associated with the Visitor pattern:

  • Violation of Encapsulation: The pattern usually requires exposing the internal details of elements to the visitor, which can be seen as a violation of encapsulation.
  • Inflexibility to Changes in Element Classes: If you add a new Element subclass, you must modify the Visitor interface and all its concrete subclasses to handle the new Element type. This makes the pattern less flexible when the element hierarchy is frequently changing.
  • Overhead: The pattern introduces additional classes and interfaces, which can be seen as an overhead if the number of operations or elements is small.

When deciding to use the Visitor pattern, weigh the benefits against the drawbacks and consider the specifics of your problem domain.

In the next article, I will discuss the Strategy Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Visitor Design Patterns in C#. I hope you enjoy this Visitor Design Pattern Real-Time Examples using the C# article.

Leave a Reply

Your email address will not be published. Required fields are marked *