Definition: Encapsulates certain operations that act on each element in a certain data structure. It can define new operations that act on these elements without changing the data structure.
Type: Behavioral Pattern
Class diagram:
example:
For example, think about adding different types of goods to the cart, and when clicking on checkout, it calculates the fees to be paid for all the different goods. Now, the calculation logic is to calculate the prices of these different types of goods. Or in other words, we transfer this logic to another class through the visitor mode. Let's implement this example of visitor pattern.
To implement the visitor pattern, the first thing to do is to create a class that can be added to the shopping cart to represent different types of items (itemElements).
ItemElement.javapackage com.journaldev.design.visitor;public interface ItemElement { public int accept(ShoppingCartVisitor visitor);}Note that the accept method accepts the visitor as a parameter. Of course, there are other ways to specify detailed products here, but for simplicity, there is no need to consider too much details here, only focusing on the visitor mode.
Now create some entity classes for different products.
Book.java
package com.journaldev.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); }}Fruit.java
package com.journaldev.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); }} Note that the implementation of the accept() method is in the entity class, which calls the visitor's visit() method to pass the current class object as its own parameter.
Here, the visit() method used for different types of goods will be implemented in the entity class of the visitor interface.
ShoppingCartVisitor.java
package com.journaldev.design.visitor;public interface ShoppingCartVisitor { int visit(Book book); int visit(Fruit fruit);}The visitor interface will now be implemented and the logic of calculating its own expenses for each product.
ShoppingCartVisitorImpl.java
package com.journaldev.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; }}Now let's see how to use it in the program.
ShoppingCartClient.java
package com.journaldev.design.visitor;public class ShoppingCartClient { public static void main(String[] args) { ItemElement[] items = new ItemElement[]{new Book(20, "1234"),new Book(100, "5678"), new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")}; int total = calculatePrice(items); System.out.println("Total Cost = "+total); } private static int calculatePrice(ItemElement[] items) { ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl(); int sum=0; for(ItemElement item : items){ sum = sum + item.accept(visitor); } return sum; }}When running the above program is, we get the following output.
Book ISBN::1234 cost =20Book ISBN::5678 cost =95Banana cost =20Apple cost =25Total Cost =160
Please note that the implementation here seems to be the same as the accept() method for all products, but it can also be different. For example, if the product is empty, it can perform logical checks and no longer calls the visit() method.
Advantages of Visitor Mode:
Comply with the principle of single responsibility: In any scenario where the visitor mode is applicable, the operations that need to be encapsulated in the visitor in the element class must be operations that have little to do with the element class itself and are volatile. On the one hand, the use of the visitor mode complies with the principle of single responsibility, and on the other hand, because the encapsulated operations are usually volatile, when changes occur, the expansion of the changing part can be achieved without changing the element class itself.
Good scalability: Element classes can extend different operations by accepting different visitors.
Applicable scenarios for visitor mode:
If there are some operations in an object that are not related to the object (or weakly related) and in order to avoid these operations contaminating the object, you can use the visitor mode to encapsulate these operations into the visitor.
If there are similar operations in a group of objects, in order to avoid a large number of duplicate code, these duplicate operations can also be encapsulated into the visitor.
However, the visitor mode is not that perfect, and it also has fatal flaws: adding new element classes is more difficult. Through the code of the visitor pattern, we can see that in the visitor class, each element class has its corresponding processing method. That is to say, each element class needs to be added to modify the visitor class (also including the subclass or implementation class of the visitor class), which is quite troublesome to modify. That is to say, when the number of element classes is uncertain, the visitor mode should be used with caution. Therefore, the visitor mode is more suitable for refactoring existing functions. For example, if the basic functions of a project have been determined, the data of element classes have been basically determined and will not change. All that will change is the relevant operations within these elements. At this time, we can use the visitor mode to refactor the original code, so that the original functions can be modified without modifying each element class.
Summarize:
As GoF, the author of Design Pattern, describes the visitor mode: in most cases, you need to use the visitor mode, but once you need it, you really need it. Of course this is only for the real big guys. In reality (at least in the environment I am in), many people are often addicted to design patterns. When using a design pattern, they never seriously consider whether the pattern they are using is suitable for this scenario, but often just want to show their ability to control object-oriented design. If you have this mentality when programming, you often abuse the design pattern. Therefore, when learning design patterns, you must understand the applicability of the patterns. It is necessary to use a pattern because you understand its advantages, not to use a pattern because you understand its disadvantages; rather than to use a pattern because you do not understand its disadvantages, not to use a pattern because you do not understand its advantages.