In this tutorial, we will solve a real-world problem using Visitor pattern and explain ideas behind visitor pattern. Core idea of the visitor pattern is to decouple operations that can be done on family of the classes from the classes itself!

Problems that are solved using the visitor pattern

Typically, you have some classes which are very similar(family of classes) and those classes extends one base class(in my example base class is Form with concrete classes BasicIncidentData , BasicInfoAboutCar, etc.) and you need to support performing multiple operations on group of those classes(in my case operations were archive and submit).

When you identify a situation where you have a group of classes and the need to perform multiple operations on them, it’s time for visitor pattern. Just create two hierarchies, connect them like in this example that I will show you and that’s it!

Real-world business problem that I had in my real job

Our team developed a software solution dedicated to calculating insurance costs for an insurance company. There were two use cases when customers interact with an application:

  • When you buy an insurance
  • When you have an incident

In both cases, you are prompted to populate some forms. For example, when you buy an insurance, you first populate basic info about yourself, then in the next form you populate info about your car, then you select the kind of insurance, etc.

In case of an incident, you populate one form that shortly describes the incident(date and time when the incident happened, who was driving the vehicle, etc.), then based on what was populated in the first form there is another form where you mention which part of your vehicle is damaged, etc. Also, it was important to remember form history so reverting to the past states is possible!

Of course, when every form is submitted, we must process it on the backend side (save data in the database or send a message to the Kafka, depending on the form). But processing the form is not the only operation that we want to do with our form! There is also an archive operation when some form is not edited for 1 year, this means that data of the form should be changed in some way, according to archiving rules.

Wait, isn’t this problem too complex for visitor pattern?

You are tottaly right. The problem that we had in front of us is a combination of visitor, state and memento pattern. In this article we will solve only one part of the problem and that is decoupling operation that can be performed on the form from the form itself! If you want to see the solution to the whole problem and how we combined all three patterns, check out this article!

First try to solve the problem

We were in hurry to meet deadlines, so this was our first try:

public interface Form {
}

public class BasicIncidentData implements Form {
        private LocalDate dateAndTime;
        //other incident data
}
public class BasicInfoAboutCar implements Form {
        private String carType;
        //other data about car
}

and we created a lot of other forms in this fashion(we had around 40 forms like this, one class per different form that can be populated)

So far so good, we are on a good track. Now, we want to process those forms. Here is a try:

public class FormService {
    public void submit(Form form) {
        //breakpoint 1
        if (form instanceof  BasicIncidentData) {
            BasicIncidentData castedForm = (BasicIncidentData) form;
            //submit by saving it into the database -> ~ 20-25 lines of code
        }
        //breakpoint 2
        if (form instanceof  BasicInfoAboutCar) {
            BasicInfoAboutCar castedForm = (BasicInfoAboutCar) form;
            //submit by saving it into the database and sending Kafka message -> ~ 20-25 lines of code
        }//40 more if clauses for 40 more forms

    }
}

and the client code(very simplified for the sake of example):

class FormController {
    private final FormService formService;

    public FormController(FormService formService) {
        this.formService = formService;
    }

    void submit(FormResource formResource) {
        Form form = formResource.toForm();
        formService.submit(form);
    }
}

Important note: It is important to notice that in the client code of the FormService (in this case FormController) the specific form being submitted is unknown. Also, form type check is done inside FormService!

Here is where the mess started. Our codebase wasn’t ideal, so for every form we had around 20-25 lines for submitting. Multiply that by 40 forms. Oh boy, here we are at the submit function inside FormService which has almost 1000 lines. Should I mention that this smells bad and cannot be unit tested?

Also, it is worth mentioning that it wasn’t only one place where we had this situation with instanceof and handling every form differently. We had an archive operation. And the logic was very similar. Instead of submit method, we had an archive method and then the same long instanceof garbage code and another 1000 lines of code for archiving.

A thousand lines of code inside one method is a red flag. We want to split that method. Let’s see how to do that and what are the benefits.

Discovering a bug too late

If you look again at the submit method you will notice that even if you remove everything between breakpoint 1 and breakpoint 2, code will still compile! You will discover a bug only at runtime when you try to submit BasicIncidentData form! We want some solution where you just create BasicIncidentData class and if you forget to implement some methods that are mandatory, Java complains by not compiling!

Open closed principle violated

We want to be open for extension(which means we can add new features) but closed for modification (which means we don’t want to update current code that works). It would be great if we can just add new class/function for every new form and that we don’t need to update classes for forms that already exists and works!

With our current state of code, when we add a new form, we are modifying submit and archive methods in FormService class which means that we are not closed for modification. We can screw up the already working submit and archive method. We want to achieve state where when we add a new form, we need to add a new method or class (instead of modifying existing methods) and properly write a unit test for that new method and don’t touch existing methods of existing forms.

How this problem should be solved with the visitor pattern

We realized that we have 2 different hierarchies. One is Form and its subclasses(one per form type) and another is FormOperation and its subclasses(one per operation that can be performed on the form). We can perform multiple operations on our forms. For example, we can submit our forms(when the user populates a form on the frontend and hits submit), we can archive forms(when some form is not edited for 1 year, this means that data of the form should be changed in some way, according to archiving rules), etc. We already saw Form hierarchy, let’s see the hierarchy of the FormOperation:

interface FormOperation {
    void doOperation(BasicIncidentData basicIncidentData);
    void doOperation(BasicInfoAboutCar basicInfoAboutCar);
    //38 more overloaded methods, one per form
}
public class SubmitFormOperation implements FormOperation {
    public void doOperation(BasicIncidentData basicIncidentData) {
       //here we submit BasicIncidentData form
    }
    public void doOperation(BasicInfoAboutCar basicInfoAboutCar) {
        //here we submit BasicInfoAboutCar form
    }
    //38 more overloaded methods, one per form
}
public class ArchiveFormOperation implements FormOperation {
    public void doOperation(BasicIncidentData basicIncidentData) {
       //here we archive BasicIncidentData form
    }
    public void doOperation(BasicInfoAboutCar basicInfoAboutCar) {
        //here we archive BasicInfoAboutCar form
    }
    //38 more overloaded methods, one per form
}

Ok, so we divided one method of 1000 lines into 40 methods of 25, good job. We can independently test every method, when we add a new form, we will just add a new method(we are aligned with the open/closed principle now). Also, if we add a new operation, we just need to implement FormOperation, we will not touch the old operations code.

Note: Those two hierarchies are not connected in any way! You should note that this situation with two hierarchies really reminds of a bridge pattern! Can you spot why is this a visitor pattern and not bridge?
Let’s just replace the old broken FormService with our new FormOperation inside our client:

class FormController {
    private final FormOperation formOperation;

    public FormController(FormOperation formOperation) {
        this.formOperation = formOperation;
    }

    void submit(FormResource formResource) {
        Form form = formResource.toForm();
        formOperation.doOperation(form);//compilation error
    }
}

Seems like we are at the beginning, this compilation problem is why we used instanceof in the first place. But here we introduce the elegant part of the solution. We connect our hierarchies. Let’s add a new method inside Form:

public interface Form {
  public abstract void doOperation(FormOperation formOperation);
}

Also, we must implement doOperation in concrete forms. Let’s add doOperation inside concrete forms:

public class BasicIncidentData implements Form {
  public void doOperation(FormOperation formOperation) {
     formOperation.doOperation(this);
  }
}
public class BasicInfoAboutCar implements Form {
  public void doOperation(FormOperation formOperation) {
     formOperation.doOperation(this);
  }
}

Now, we can refactor our controller to this:

class FormController {
    private final SubmitFormOperation submitFormOperation;

    public FormController(SubmitFormOperation submitFormOperation) {
        this.submitFormOperation = submitFormOperation;
    }

    void submit(FormResource formResource) {
        Form form = formResource.toForm();
        form.doOperation(submitFormOperation);//no problems now
    }
}

Bug discovery time

Important thing to notice in the new solution which is using visitor pattern is that we cannot make the same bug that will be revealed at runtime! You can try to create new form and on purpose to forget to implement some method that is needed and you will see that Java won’t compile without all necessary methods!

The Last small problem

The last problem is that our controller supports only SubmitFormOperation. If we want to support ArchiveFormOperation, we will need to put that ArchiveFormOperation as a dependency, and our controller will be aware of that. It would be nice if there is some manager class or something similar that will handle those operations and the controller only has that manager as a dependency. Something that will hide internal details of the forms, operations, etc. Ah yes, we want to simplify things for the client and to hide unnecessary details from him. That’s what the facade patter does!

Let’s introduce that (very simple and not rich) facade

public class FormFacade {
    private SubmitFormOperation submitFormOperation;
    private ArchiveFormOperation archiveFormOperation;

    public void submit(Form form) {
        form.doOperation(submitFormOperation);
    }
    public void archive(Form form) {
        form.doOperation(archiveFormOperation);
    }

}

and we want to use our facade inside the controller now:

class FormController {
    private final FormFacade formFacade;

    public FormController(FormFacade formFacade) {
        this.formFacade = formFacade;
    }

    void submit(FormResource formResource) {
        Form form = formResource.toForm();
        formFacade.submit(form);
    }
    void archive(FormResource formResource) {
        Form form = formResource.toForm();
        formFacade.archive(form);
    }
}

Now, our controller is unaware of visitor pattern, operations, and every other implementation detail.

Summary

Hope you learned how to get rid of this spaghetti code with instanceof and create an elegant solution.