CSharp example of Visitor design pattern

Jan 30, 2013 00:00 · 760 words · 4 minute read Programming Design pattern

What is the Visitor design pattern?

Give a structure some new functionality without change its structure. In fact we will have some master class that has the all logic and we will be using its knowledge in an abstracted way.

Example

What we have

DTOs

We have Printer DTO which consist of different Types of printers. Each printer has its own properties some of them are unique and some of them are common.

DTOs

public class Printer {
	public List<Hp> HpList = new List<Hp>();
	public List<Sony> SonyList =new List<Sony>();
	public List<Toshiba> ToshibaList =new List<Toshiba>();
}
public class Hp {
	public string Name { get; set; }
	public int Year { get; set; }
	public int Price { get; set; }
}
public class Sony {
	public string Name { get; set; }
	public int Year { get; set; }
	public int Price { get; set; }
	public int Discount { get; set; }
}
public class Toshiba {
	public string Name { get; set; }
	public int Price { get; set; }
	public bool Colorful { get; set; }
}
Payment

On the other hand we have our payment class that tries to calculate the customer bill.

payment

public int CalculateTheBill() {
	Printer printer=new Printer();
	printer.HpList.Add(new Hp {Name = "HP100",Price = 100,Year = 1999});
	printer.HpList.Add(new Hp {Name = "HP200",Price = 200,Year = 2010});
	printer.SonyList.Add(new Sony {Name = "SonyPX1",Price = 220,Year = 2010,Discount = 70});
	printer.ToshibaList.Add(new Toshiba { Name = "Toshiba600", Price = 800, Colorful = true});
	int totalPrice = 0;
	foreach (var hp in printer.HpList) {
		totalPrice += hp.Price;
	}

	foreach (var sony in printer.SonyList) {
		totalPrice += (sony.Price - sony.Discount);
	}
	
	foreach (var toshiba in printer.ToshibaList) {
		totalPrice += toshiba.Price;
	}

	return totalPrice;
}

The method is iterating through each list and add its value to the total.

That is a way to solve the problem but it become frustrating when we want to add more printers to the bill or want to add more functionality such as calculating discount and so on.

Visitor Pattern

To apply the visitor pattern in this situation , we have to abstract the overloads and ask the visitor to find its appropriate overload.

Abstract overloads

Make an interface that consists of Visitor overloads according to our require printer types.

public interface IVisitor {
	void Visitor(Hp hp);
	void Visitor(Sony sony);
	void Visitor(Toshiba toshiba);
}
Abstract input types

Make interface for printers, so it helps us to have the general abstract type of them

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

It has Accept method that can help us each printer to the visitor.

Implement IVisitor

Making another class that wish to have our business logic and ask it to implement from IVisitor.

public class Billing : IVisitor {
	public int totalPrice = 0;
	public void Visitor(Hp hp) {
		totalPrice += hp.Price;
	}
	
	public void Visitor(Sony sony) {
  totalPrice += (sony.Price-sony.Discount);
	}
	
	public void Visitor(Toshiba toshiba) {
		totalPrice += (toshiba.Price);
	}
}
Implement IMachine

Ask your printer types to implement from IMachine interface to generalize your printers.

Implementing

public class Hp:IMachine {
	public string Name { get; set; }
	public int Year { get; set; }
	public int Price { get; set; }
	public void Accept(IVisitor visitor) {
		visitor.Visitor(this);
	}
}

Ask each one to pass themselves to the visitor that can help us to hide the overload behind interface.

Use interface list

Rather than using a concrete type for each list , use your interface.

Impelementing

public class Printer:IMachine {
	public List<IMachine> PrinterList = new List<IMachine>();
	public void Accept(IVisitor visitor) {
		foreach (var machine in PrinterList) {
			machine.Accept(visitor);
		}
	}
}

Now here we are going to loop through the printer list and ask to pass visitor.

Using it

Now all left to do is to call the accept method and pass our business logic class to it .

using it

public int CalculateTheBill() {
	Printer printer=new Printer();
	printer.PrinterList.Add(new Hp {Name = "HP100",Price = 100,Year = 1999});
	printer.PrinterList.Add(new Hp { Name = "HP200", Price = 200, Year = 2010 });
	printer.PrinterList.Add(new Sony { Name = "SonyPX1", Price = 220, Year = 2010, Discount = 70 });
	printer.PrinterList.Add(new Toshiba { Name = "Toshiba600", Price = 800, Colorful = true });
	Billing billing=new Billing();
	printer.Accept(billing);
	return billing.totalPrice;
}

What will be happening

The Accept method in the Printer class will be called and it goes through printer list and call the Accept method of each one . Then the appropriate overload of the Billing class will be called.

Download

Feel free to download the full source code of this Visitor pattern example from my GitHub.