Design Patterns - Visitor and Template

22 juli 2022 om 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we discussed the state and strategy design patterns, which fall under the category of behavioral design patterns. Today, we will explore two more design patterns from the same category called Visitor and Template.

Table of contents

  1. Introduction to Visitor Design Pattern
  2. Implementation of Visitor Design Pattern
  3. Introduction to Template Design Pattern
  4. Implementation of Template Design Pattern
  5. Conclusion

Introduction to Visitor Design Pattern

Visitor design pattern helps in the separation of data structure and algorithms. By doing this, whenever the visitor changes the logic to execute, it changes the algorithm that needs to be applied to an element. This is achieved by passing the visitor object to the element object which can then be changed at runtime.

Implementation of Visitor Design Pattern

The visitor design pattern mainly consists of:

  1. IComponent
  2. Concrete Component
  3. IVisitor
  4. Concrete Visitor

Here is a template for the implementation of the Visitor Design Pattern -

using System;

namespace PARTECH_Visitor
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var componentA = new ConcreteComponentA();
            var componentB = new ConcreteComponentB();

            var visitor1 = new ConcreteVisitorA();
            var visitor2 = new ConcreteVisitorB();

            componentA.Accept(visitor1);
            componentA.Accept(visitor2);    
            componentB.Accept(visitor1);
            componentB.Accept(visitor2);

            Console.ReadLine();
        }
    }

    public interface IComponent
    {
        void Accept(IVisitor visitor);
    }

    public interface IVisitor
    {
        void VisitComponentA(ConcreteComponentA component);
        void VisitComponentB(ConcreteComponentB component);
    }

    public class ConcreteComponentA : IComponent
    {
        public void Accept(IVisitor visitor)
        {
            visitor.VisitComponentA(this);
        }

        public string DoActionA()
        {
            return "Started executing the concrete component A.";
        }
    }

    public class ConcreteComponentB : IComponent
    {
        public void Accept(IVisitor visitor)
        {
            visitor.VisitComponentB(this);
        }

        public string DoActionB()
        {
            return "Started executing the concrete component B.";
        }
    }

    public class ConcreteVisitorA : IVisitor
    {
        public void VisitComponentA(ConcreteComponentA element)
        {
            Console.WriteLine(element.DoActionA() + " + from ConcreteVisitorA");
        }

        public void VisitComponentB(ConcreteComponentB element)
        {
            Console.WriteLine(element.DoActionB() + " +  from ConcreteVisitorA");
        }
    }

    public class ConcreteVisitorB : IVisitor
    {
        public void VisitComponentA(ConcreteComponentA element)
        {
            Console.WriteLine(element.DoActionA() + " + from ConcreteVisitorB");
        }

        public void VisitComponentB(ConcreteComponentB element)
        {
            Console.WriteLine(element.DoActionB() + " +  from ConcreteVisitorB");
        }
    }
}

Here,
IComponent is an interface that exposes the declaration of the method Accept, which in turn accepts the Visitor object for execution.

IVisitor is another interface that holds the methods for visit operations (concrete visitors). Concrete components A and B implement the interface IComponent and take up the concrete visitor as input and call the visit method in the visitor class by passing the current component object to it.

Concrete Visitors A and B implement the interface IVisitor and have the algorithm logic. Inside the methods, it has the logic to call the DoAction A and B methods.

The main method is the client here. It creates 4 objects; 2 for concrete visitors and 2 for concrete components. The accept method is called from each component by passing both the visitors and the output of the code observed.

Thus by changing the visitor (that is passed to a component), the action to be performed on the component can be altered without changing the code to handle them. This could come in handy in scenarios where we have to isolate the modification of the properties in the data structure from one another. It can also come in handy where visitors take up the logic to modify one property at a time.

On executing the above code, the below output gets printed -

Introduction to Template Design Pattern

The template design pattern also falls under the behavioral design pattern category. It helps in defining algorithms in a step-by-step procedure. It allows individual step modification. On a high level, instead of replacing the entire algorithm, it provides ways to change individual steps present inside it. It can be used in scenarios where the algorithm steps have to remain the same, but the implementation of it varies based on the inherited sub-class.

Implementation of Template Design Pattern

The template design pattern mainly consists of:

  1. Abstract class
  2. Concrete class

Here is a template used to implement the Template Design Pattern -

using System;

namespace PARTECH_Template
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var concreteA = new ConcreteClassA();
            concreteA.DoAction();

            var concreteB = new ConcreteClassB();
            concreteB.DoAction();

            Console.ReadLine();
        }
    }

    public abstract class AbstractClass
    {
        public abstract void Operation1();
        public abstract void Operation2();
        public abstract void Operation3();
        public void DoAction()
        {
            Operation1();
            Operation2();
            Operation3();
        }
    }

    public class ConcreteClassA : AbstractClass
    {
        public override void Operation1()
        {
            Console.WriteLine("Executing Operation 1 from Concrete Class A");
        }

        public override void Operation2()
        {
            Console.WriteLine("Executing Operation 2 from Concrete Class A");
        }

        public override void Operation3()
        {
            Console.WriteLine("Executing Operation 3 from Concrete Class A");
        }
    }

    public class ConcreteClassB : AbstractClass
    {
        public override void Operation1()
        {
            Console.WriteLine("Executing Operation 1 from Concrete Class B");
        }

        public override void Operation2()
        {
            Console.WriteLine("Executing Operation 2 from Concrete Class B");
        }

        public override void Operation3()
        {
            Console.WriteLine("Executing Operation 3 from Concrete Class B");
        }
    }
}

Here,

The abstract class contains abstract methods Operation 1, Operation 2, and Operation 3. And it also has a DoAction method, where the abstract methods are referred to sequentially. Concrete Classes A and B implement the abstract class and have their logic for Operations 1, 2, and 3. Though they both follow the same steps in executing the method, the logic inside the method can be different during the implementation phase. This helps them to achieve different actions by calling the required class at runtime.

The main method is the client here, where the objects for concrete classes A and B are created. And with the objects, the DoAction method is called. For your understanding, we have written different log texts in this method implemented by the concrete classes.

The template design pattern can be used in scenarios where the steps involved in achieving the result are the same, but the logic inside each step varies.

It is simple in structure, as it involves lesser components and provides better code understanding. On executing the above code, the below output gets generated -

Conclusion

And that’s what Visitor and Template Behavioral design patterns are all about. With this, we have completed the entire design patterns blog series that spanned multiple categories such as creational, structural, and behavioral categories.

Nieuwste