We are taking care of another design pattern, and it is Visitor, now entries can be irregular, I have some responsibilities, but a series of design patterns I want to finish :) We go with the theme :)
Discussion
The main purpose of the Visitor's pattern is to add new functionalities to an existing object without modifying the class on which the object operates. As you can see, this pattern does not violate the open-closed principle as you can see on the picture below :) .
The implementation in short looks like, the Visitor class declares the visit() method, whose argument is an element of the structure and elements of the structure declare the accept() method, which accepts as argument the Visitor class, the accept() method calls the visit() method in the Visitor class depending on the type of argument passed.
Visitor cooperates well with Composite, so in practice they can be used together to manage eg the structure of employees in the company. The Composite is responsible for the structure and the Visitor is responsible for assigning tasks to each employee.
Intent
- Represents operations performed on structure objects also allows you to define a new operation without changing the class of the element on which it operates.
- Separates the algorithm from the structure it operates on.
- Double dispatch, i.e. calling the visitor's methods depending on the type of parameter pass to the method.
Problem
Many operations must be performed on separate elements of the object structure. In this case, it will be good to use the Visitor pattern, because the class visitor iterates efficiently after each element in the structure of objects.
Use when:
- You have the structure of objects and you have to perform some operation on every element of this structure.
- You want to add or change operations performed on structure elements without changing the class of this element.
Structure
Let's see how the Visitor pattern looks like
The client first saves the elements in the structure, then creates the visitors classes objects and subsequently iterates over the elements of the structure.
Let's see how it looks in the code, let's start with the Visitor class and classes that inherit from it:
namespace VisitorSchema
{
abstract class Visitor
{
public abstract void VisitElement(ElementA elementA);
public abstract void VisitElement(ElementB elementB);
}
}
And classes that inherit from the Visitor class, or you can say specific visitors :) implementing abstract methods.
namespace VisitorSchema
{
class Visitor1 : Visitor
{
public override void VisitElement(ElementA elementA)
{
Console.WriteLine(elementA.GetType().Name + " visited by " + GetType().Name);
}
public override void VisitElement(ElementB elementB)
{
Console.WriteLine(elementB.GetType().Name + " visited by " + GetType().Name);
}
}
}
And the second Visitor2.
namespace VisitorSchema
{
class Visitor2 : Visitor
{
public override void VisitElement(ElementA elementA)
{
Console.WriteLine(elementA.GetType().Name + " visited by " + GetType().Name);
}
public override void VisitElement(ElementB elementB)
{
Console.WriteLine(elementB.GetType().Name + " visited by " + GetType().Name);
}
}
}
We will call methods depending on the type of argument, i.e. in our case the class name as we can see we use method overloading.
Let's see what class we pass to the VisitElement() methods. We have an Abstract class Element first.
namespace VisitorSchema
{
abstract class Element
{
public abstract void Accept(Visitor visitor);
}
}
As you can see, we have defined the Accept() method in which we will call VisitElement() methods in specific classes that inherit from the Element class, depending on the class name.
Let's see the classes inheriting from the Element class.
namespace VisitorSchema
{
class ElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitElement(this);
}
}
}
And ElementB.
namespace VisitorSchema
{
class ElementB : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitElement(this);
}
}
}
As you can see the ElementB and ElementA classes do not differ much, just the name of the class, we will pass the name of the class as an argument to the VisitElement() method, thanks to which the compiler will know which version of the VisitElement() method must to invoke.
All created elements are saved in the structure of objects and we iterate through its elements by calling the Accept() method.
namespace VisitorSchema
{
class ObjectStructure
{
private List<Element> _elements = new List<Element>();
public void Attach(Element element)
{
_elements.Add(element);
}
public void Detach(Element element)
{
_elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element element in _elements)
{
element.Accept(visitor);
}
}
}
}
We create elements of the structure in the client, also from the client's level we call the Accept() method, followed by iteration over the elements of this structure.
namespace VisitorSchema
{
class MainApp
{
static void Main()
{
ObjectStructure o = new ObjectStructure();
o.Attach(new ElementA());
o.Attach(new ElementB());
Visitor v1 = new Visitor1();
Visitor v2 = new Visitor2();
o.Accept(v1);
o.Accept(v2);
Console.ReadKey();
}
}
}
Result:
Time for a lame joke :)
Why can't you trust trees?
Because they are shady.
I know, I have to look for some better jokes :) , I'm average in this
Real-life example
Taxi company
Let's now make an example on the principle of a transport company, the structure class of objects is a list of clients, the customer is one of the elements of this list, and the taxi is an example of the Visitor class. As they say, the picture means more than a thousand words :)
As in the previous example, let's start with the visitors in our case it is a taxi.
namespace taxi
{
abstract class Taxi
{
public abstract void VisitCustomer(TaxiCustomer customer);
public abstract void VisitCustomer(PoorCustomer poorCustomer);
public abstract void VisitCustomer(RichCustomer richCustomer);
}
}
We define the VisitCustomer() method, which can accept as an argument three different classes of ordinary customer, rich and poor. Let's see the classes that implement the VisitCustomer() method.
namespace taxi
{
class Taxi1 : Taxi
{
public override void VisitCustomer(TaxiCustomer customer)
{
Console.WriteLine("Customer " + customer.name + " has been visited by " + GetType().Name);
}
public override void VisitCustomer(PoorCustomer poorCustomer)
{
Console.WriteLine("Customer " + poorCustomer.name + " can only pay 20$ for " + GetType().Name);
}
public override void VisitCustomer(RichCustomer richCustomer)
{
Console.WriteLine("Customer " + richCustomer.name + " can pay 100$ for " + GetType().Name);
}
}
}
And:
namespace taxi
{
class Taxi2 : Taxi
{
public override void VisitCustomer(TaxiCustomer customer)
{
Console.WriteLine("Customer " + customer.name + " has been visited by " + GetType().Name);
}
public override void VisitCustomer(PoorCustomer poorCustomer)
{
Console.WriteLine("Customer " + poorCustomer.name + " cannot go " + GetType().Name + "." + " He doesn't have money.");
}
public override void VisitCustomer(RichCustomer richCustomer)
{
Console.WriteLine("Customer " + richCustomer.name + " can pay 300$ for " + GetType().Name);
}
}
}
Let's see the customer classes now.
First, the customer's abstract class in which the most important things are defined:
namespace taxi
{
abstract class Customer
{
public string name;
public abstract void Accept(Taxi taxi);
}
}
RichCustomer
namespace taxi
{
class RichCustomer : Customer
{
public RichCustomer(string Name)
{
name = Name;
}
public override void Accept(Taxi taxi)
{
taxi.VisitCustomer(this);
}
}
}
PoorCustomer
namespace taxi
{
class PoorCustomer : Customer
{
public PoorCustomer(string Name)
{
name = Name;
}
public override void Accept(Taxi taxi)
{
taxi.VisitCustomer(this);
}
}
}
TaxiCustomer
namespace taxi
{
class TaxiCustomer : Customer
{
public TaxiCustomer(string Name)
{
name = Name;
}
public override void Accept(Taxi taxi)
{
taxi.VisitCustomer(this);
}
}
}
All customers are obviously enrolled in the structure of objects.
namespace taxi
{
class CustomerStructure
{
private List<Customer> _customers = new List<Customer>();
public void Attach(Customer customer)
{
_customers.Add(customer);
}
public void Detach(Customer customer)
{
_customers.Remove(customer);
}
public void Accept(Taxi taxi)
{
foreach (Customer customer in _customers)
{
customer.Accept(taxi);
}
}
}
}
In the client, we save customers using the transport company's services in the structure and iterate through the customer structure.
namespace taxi
{
class Program
{
static void Main(string[] args)
{
CustomerStructure o = new CustomerStructure();
o.Attach(new TaxiCustomer("Sam"));
o.Attach(new TaxiCustomer("John"));
o.Attach(new TaxiCustomer("Carl"));
o.Attach(new PoorCustomer("David"));
o.Attach(new RichCustomer("McOnnel"));
Taxi t1 = new Taxi1();
Taxi t2 = new Taxi2();
o.Accept(t1);
Console.WriteLine();
o.Accept(t2);
Console.ReadKey();
}
}
}
Result:
Relations with other design patterns
- The Iterator can traverse the entire structure of the Composite. The visitor can perform operations on elements after which he iterates.
- The visitor can be used together with the iterator to be able to iterate through the objects structure and perform operations on some of these objects.
- The Interpreter pattern syntax tree is a Composite, so the Iterator and Visitor can also be used.
Summary
That’s all about Visitor.
Link to github with the whole code from this article: https://github.com/Slaw145/Visitor
This content also you can find on my blog: http://devman.pl/programtech/designpatterns/design-patterns-visitor/
And on medium: https://medium.com/@sawomirkowalski/design-patterns-visitor-bb59c5883208
In the next article, we will talk about the Memento pattern.
And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place
– site on fb: Devman.pl-Slawomir Kowalski
– group on fb: DevmanCommunity
Ask, comment underneath at the end of the post, share it, rate it, whatever you want.
Illustrations, pictures and diagrams are from: https://sourcemaking.com/design_patterns/visitor