Thursday, December 22, 2022

Visitor Design Patterns In Java Examples Tutorial

Hello guys, if you want to learn Visitor design pattern in Java then you have come to the right place. Earlier, I have covered many design patterns like Decorator, Strategy, State, Composite, Adapter, Command, Template, Factory, Observer and even few Microservice patterns like SAGA and Database per service and in this article, I will talk about Visitor Design Pattern and how to implement in Java. You will learn things like what is Visitor design pattern, what problem it solves, what are pros and cons of Visitor design pattern, when to use Visitor pattern as well as any alternatives of Visitor Pattern in Java. I will also show you a real world example of Visitor design pattern, but, before we get to the 5 best examples that will teach you all about design patterns in Java, let me tell you a little bit more about what it really is.

Simply put, design patterns are used for solving problems that may occur in a pattern. There are also things like behavioral design patterns that can identify communication patterns of objects. The visitor pattern is one such behavioral pattern. 

The visitor design pattern can be used for separating an algorithm from an object on which it operates. This will allow you to add new operations to existing object structures without the need for modifying those structures. 

This will also give you the flexibility to add methods to any object hierarchy without modifying the written code. 

Visitor Design Pattern in Java

Basic Definitions

Visitor: It is an abstract class that can be used for declaring the visit operations for all types of visitable classes. 

ConcreteVisitor: Each new Visitor will be responsible for all the new operations. 

Visitable: It is basically an interface that declares the accepted operation. This is actually the entry point for an object to be visited.

ConcreteVisitable: Those classes implement the Visitable interface or class and define the accept operation. The visitor object is passed to this object using the accept operation.


The UML Diagram

In the UML diagram, we have two implementation hierarchies: specialized visitors and concrete elements.

The client uses a Visitor implementation and applies it to the object structure. The composite object iterates over its components and applies the visitor to each of them.

This method is the same for all elements in the structure, it performs double dispatch by passing itself (via this keyword) to the visitor's visit method.


Visitor Design Patterns In Java Examples Tutorial


See the following implementation example to get a better understanding:


public class Project extends Task{


    List<Task> tasks = new ArrayList<>();


    // ...


    @Override

    public void accept(Visitor v) {

        for (Task t : this.tasks) {

            t.accept(v);

        }

    }

}


When creating the new element, name it the ProgrammingTask, we'll have to provide the implementation of this method.

Due to the nature of the Visitor pattern, the implementation will be the same, so in most cases, it would require us to copy-paste the boilerplate code from other, already existing elements:


public class ProgrammingTask extends Task{


    // ...


    public void accept(Visitor v) {

        v.visit(this);

    }

}


Let's say that we want to process our Project tasks, but each of them in a different way, depending on its class type.

Therefore, our visitor will have a separate method for the given type:


public class TaskVisitor implements Visitor {


    @Override

    public void visit(BATask ba) {

        System.out.println(

          "processing a BA Task with taskId: " + ba.taskId);

    }


    @Override

    public void visit(ProgrammingTask pt) {

        System.out.println(

          "processing a Programming Task with taskId: " + pt.taskId);

    }

}


The concrete visitor implements two methods, correspondingly one per each type of Element. This gives us access to the particular object of the structure on which we can perform necessary actions.

Now, see the following example to get a better understanding of testing:


public class VisitorDemo {


    public static void main(String[] args) {


        Visitor v = new TaskVisitor();


      Project p = new Project(generateTaskId());

        p.tasks.add(new ProgrammingTask(generateTaskId()));

        p.tasks.add(new ProgrammingTask(generateTaskId()));

        p.tasks.add(new BATask(generateTaskId()));


        p.accept(v);

    }


    // ...

}


We create a TaskVisitor, which holds the algorithm we will apply to our elements.

Next, we set up our Project with proper task and apply the visitor which will be accepted by every element of an object structure.

We will get an output like this:


processing a ProgrammingTask with taskId: P1

processing a ProgrammingTask with taskId: P2

processing a BATask with taskId: B1


Now, check out another real world examples of visitor design patterns in Java, this time a more familiar bookd and shopping cart example


package com.javarevisited.design.visitor;


public class Book implements ItemElement {


private int price;

private String isbnNumber;

public Book(int cost, String isbn){

this.price=cost;

this.isbnNumber=isbn;

}

public int getPrice() {

return price;

}


public String getIsbnNumber() {

return isbnNumber;

}


@Override

public int accept(ShoppingCartVisitor visitor) {

return visitor.visit(this);

}


}


package com.javarevisited.design.visitor;


public class Fruit implements ItemElement {

private int pricePerKg;

private int weight;

private String name;

public Fruit(int priceKg, int wt, String nm){

this.pricePerKg=priceKg;

this.weight=wt;

this.name = nm;

}

public int getPricePerKg() {

return pricePerKg;

}



public int getWeight() {

return weight;

}


public String getName(){

return this.name;

}

@Override

public int accept(ShoppingCartVisitor visitor) {

return visitor.visit(this);

}


}


You should make sure that the implementation of accept() method in concrete classes, its calling visit() method of Visitor, and passing itself as an argument.


package com.javarevisited.design.visitor;


public interface ShoppingCartVisitor {

int visit(Book book);

int visit(Fruit fruit);

}


Now we can implement a visitor interface and every item will have its own logic to calculate the cost.


package com.javarevisited.design.visitor;


public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {


@Override

public int visit(Book book) {

int cost=0;

//apply 5$ discount if book price is greater than 50

if(book.getPrice() > 50){

cost = book.getPrice()-5;

}else cost = book.getPrice();

System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);

return cost;

}


@Override

public int visit(Fruit fruit) {

int cost = fruit.getPricePerKg()*fruit.getWeight();

System.out.println(fruit.getName() + " cost = "+cost);

return cost;

}


}


Frequently Asked Questions about Visitor Design Pattern

1. What exactly are visitor design patterns in Java?

Simply put, design patterns are used for solving problems that may occur in a pattern. There are also things like behavioral design patterns that can identify communication patterns of objects. The visitor pattern is one such behavioral pattern.

2. What can a design pattern do?

The visitor design pattern can be used for separating an algorithm from an object on which it operates. This will allow you to add new operations to existing object structures without the need for modifying those structures. 

Conclusion

If you liked this article on visitor design patterns in Java, feel free to share it with your friends and family. I have no doubt that the courses in this article will transform you from a complete beginner to a Java expert within a matter of weeks or months. 
You can also drop a comment if you have any doubts about visitor design patterns, and we will get back to you as soon as possible. 

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.