Visitor design pattern

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existent object structures without modifying the structures. It is one way to follow the open/closed principle.


n essence, the visitor allows adding new virtual functions to a family of classes, without modifying the classes. Instead, a visitor class is created that implements all of the appropriate specializations of the virtual function. The visitor takes the instance reference as input, and implements the goal through double dispatch.

[Wikipedia]

Intro

In this example, we will use the Visitor design pattern by implementing a soccer team money advisor. We would like to know how much money we will get by selling tickets to the game against different teams , with different level of seniority. The stronger the team -> the more fans it has -> the higher the price that they are willing to pay for a ticket. In this code we will have 3 seniority levels: Premium , Medium , and Small teams. And 2 kinds of chargers. The first will be the income for regular games, and the other one will be for important (and therefore more expensive tickets will be sold). We will use the same methods for the different teams and take advantage of the Visitor design pattern.

Visitor

//This pattern is used when you have to perform the same operation on objects of different types.
interface Visitor {
    public int visit(PremiumTeam premiumTeam);
    public int visit(MediumTeam mediumTeam);
    public int visit(SmallTeam smallTeam);
}

ChargeMoneyVisitor

public class ChargeMoneyVisitor implements  Visitor{
        /* This is the team that hosts the game, and want to know the expected income.
        they will calculate how much money according to the seniority level of the team that they will face.
        This will be done when each received team will go to the relevant "visit" function
        and perform the calculations.
         */
    private int ticketPrice;
    private int numOfFans;
    @Override
    public int visit(PremiumTeam premiumTeam) {
            ticketPrice=premiumTeam.getTicketPrice();
            numOfFans=premiumTeam.getNumOfFans();
            System.out.println("A Match against the Premium team " + premiumTeam.getTeamName() + "! Expected income: ");
            return ticketPrice*numOfFans;
    }
    @Override
    public int visit(MediumTeam mediumTeam) {
        ticketPrice=mediumTeam.getTicketPrice();
        numOfFans=mediumTeam.getNumOfFans();
        System.out.println("A Match against the Medium team " + mediumTeam.getTeamName() + "! Expected income: ");
        return ticketPrice*numOfFans;
    }
    @Override
    public int visit(SmallTeam smallTeam) {
        ticketPrice=smallTeam.getTicketPrice();
        numOfFans=smallTeam.getNumOfFans();
        System.out.println("A Match against the Small team " + smallTeam.getTeamName() + "! Expected income: ");
        return ticketPrice*numOfFans;
    }
}

ChargeMoreMoneyVisitor

public class ChargeMoreMoneyVisitor implements  Visitor{
    //let's say the price is 200% higher at each situation.
    private int ticketPrice;
    private int numOfFans;
    @Override
    public int visit(PremiumTeam premiumTeam) {
        ticketPrice=(premiumTeam.getTicketPrice() *2);
        numOfFans=premiumTeam.getNumOfFans();
        System.out.println("A Match against the Premium team " + premiumTeam.getTeamName() + "! Expected income: ");
        return ticketPrice*numOfFans;
    }
    @Override
    public int visit(MediumTeam mediumTeam) {
        ticketPrice=(mediumTeam.getTicketPrice() *2);
        numOfFans=mediumTeam.getNumOfFans();
        System.out.println("A Match against the Medium team " + mediumTeam.getTeamName() + "! Expected income: ");
        return ticketPrice*numOfFans;
    }
    @Override
    public int visit(SmallTeam smallTeam) {
        ticketPrice=(smallTeam.getTicketPrice() *2);
        numOfFans=smallTeam.getNumOfFans();
        System.out.println("A Match against the Small team " + smallTeam.getTeamName() + "! Expected income: ");
        return ticketPrice*numOfFans;
    }
}

Visitibale

public interface Visitibale {
        // Because of method overloading it knows which method to use.
        public int accept(Visitor visitor);
    }

SmallTeam

public class SmallTeam implements Visitibale {
    private int ticketPrice;
    private String teamName;
    private int numOfFans;
    public SmallTeam(int ticketPrice, int numOfFans, String teamName){
        this.ticketPrice=ticketPrice;
        this.teamName=teamName;
        this.numOfFans=numOfFans;
    }
    @Override
    public int accept(Visitor visitor) {
        return visitor.visit(this);
    }
    public int getTicketPrice() {
        return ticketPrice;
    }
    public int getNumOfFans() {
        return numOfFans;
    }
    public String getTeamName() {
        return teamName;
    }
}

MediumTeam

public class MediumTeam implements Visitibale {
    private int ticketPrice;
    private String teamName;
    private int numOfFans;
    public MediumTeam(int ticketPrice, int numOfFans, String teamName){
        this.ticketPrice=ticketPrice;
        this.teamName=teamName;
        this.numOfFans=numOfFans;
    }
    @Override
    public int accept(Visitor visitor) {
        return visitor.visit(this);
    }
    public int getTicketPrice() {
        return ticketPrice;
    }
    public int getNumOfFans() {
        return numOfFans;
    }
    public String getTeamName() {
        return teamName;
    }
}

PremiumTeam

public class PremiumTeam implements Visitibale {
    private int ticketPrice;
    private String teamName;
    private int numOfFans;
    public PremiumTeam(int ticketPrice, int numOfFans, String teamName){
        this.ticketPrice=ticketPrice;
        this.teamName=teamName;
        this.numOfFans=numOfFans;
    }
    @Override
    public int accept(Visitor visitor) {
        return visitor.visit(this);
    }
    public int getTicketPrice() {
        return ticketPrice;
    }
    public int getNumOfFans() {
        return numOfFans;
    }
    public String getTeamName() {
        return teamName;
    }
}

Main

public class Main {
    /*
    At this example, we will use the Visitor design pattern by implementing a soccer team money advisor.
    We would like to know how much money we will get by selling tickets to the game against different teams ,
    with different level of seniority.
    The stronger the team -> the more fans it has -> the higher the price that they are willing to pay for a ticket.
    In this code we will have 3 seniority levels: Premium , Medium , and Small teams.
    And 2 kinds of chargers. The first will be the income for regular games, and the other one will be for
    important (and therefore more expensive tickets will be sold).
    We will use the same methods for the different teams and take advantage of the Visitor design pattern.
     */
    public static void main(String[] args) {
        ChargeMoneyVisitor RegularGamesCharger = new ChargeMoneyVisitor();
        ChargeMoreMoneyVisitor ImportantGamesCharger = new ChargeMoreMoneyVisitor();

        PremiumTeam maccabiHaifa = new PremiumTeam(80,40000,"Maccabi Haifa");
        MediumTeam hapoelPetachTikva = new MediumTeam(53,5480,"Hapoel Petach Tikva");
        SmallTeam hapoelMarmorek = new SmallTeam(37,421,"Hapoel Marmorek");

        System.out.println("Regular game prices:");
        System.out.println(maccabiHaifa.accept(RegularGamesCharger));
        System.out.println(hapoelPetachTikva.accept(RegularGamesCharger));
        System.out.println(hapoelMarmorek.accept(RegularGamesCharger));
        System.out.println("----------------------------------------------");

        System.out.println("Important game prices:");
        System.out.println(maccabiHaifa.accept(ImportantGamesCharger));
        System.out.println(hapoelPetachTikva.accept(ImportantGamesCharger));
        System.out.println(hapoelMarmorek.accept(ImportantGamesCharger));
    }
}

Running Example

Regular game prices:
A Match against the Premium team Maccabi Haifa! Expected income: 
3200000
A Match against the Medium team Hapoel Petach Tikva! Expected income: 
290440
A Match against the Small team Hapoel Marmorek! Expected income: 
15577
----------------------------------------------
Important game prices:
A Match against the Premium team Maccabi Haifa! Expected income: 
6400000
A Match against the Medium team Hapoel Petach Tikva! Expected income: 
580880
A Match against the Small team Hapoel Marmorek! Expected income: 
31154