24 Behavioral Pattern-Visitor Pattern

1 Introduction to visitor mode

The visitor pattern is rarely used in actual development because it is difficult to implement and applying this pattern may lead to poor readability and maintainability of the code. It is not recommended to use it unless it is particularly necessary. Visitor mode.

2 Principle of visitor mode


3 Visitor mode implementation

Let’s take supermarket shopping as an example. Assume that three categories of commodities are sold in the supermarket: fruits, candies, and drinks. We can ignore the pricing method of each commodity, because the final checkout is handled centrally by the cashier, and pricing is added to the commodity category. The method is unreasonable design. Let’s first define candy, alcohol, and fruit.

/**
 *Abstract product parent class
 **/
public abstract class Product {<!-- -->

    private String name; //product name

    private LocalDate produceDate; //Production date

    private double price; //commodity price

    public Product(String name, LocalDate produceDate, double price) {<!-- -->
        this.name = name;
        this.produceDate = produceDate;
        this.price = price;
    }

    public String getName() {<!-- -->
        return name;
    }

    public void setName(String name) {<!-- -->
        this.name = name;
    }

    public LocalDate getProduceDate() {<!-- -->
        return produceDate;
    }

    public void setProduceDate(LocalDate produceDate) {<!-- -->
        this.produceDate = produceDate;
    }

    public double getPrice() {<!-- -->
        return price;
    }

    public void setPrice(double price) {<!-- -->
        this.price = price;
    }
}
/**
 * Candy
 **/
public class Candy extends Product implements Acceptable{<!-- -->

    public Candy(String name, LocalDate produceDate, double price) {<!-- -->
        super(name, produceDate, price);
    }

    @Override
    public void accept(Visitor visitor) {<!-- -->
        //Call the visitor in the accept method and pass this back.
        visitor.visit(this);
    }
}
/**
 * Drinks
 **/
public class Wine extends Product implements Acceptable{<!-- -->

    public Wine(String name, LocalDate produceDate, double price) {<!-- -->
        super(name, produceDate, price);
    }

    @Override
    public void accept(Visitor visitor) {<!-- -->
        visitor.visit(this);
    }
}
/**
 * Fruits
 **/
public class Fruit extends Product implements Acceptable{<!-- -->

    private double weight; //weight

    public Fruit(String name, LocalDate produceDate, double price, double weight) {<!-- -->
        super(name, produceDate, price);
        this.weight = weight;
    }

    public double getWeight() {<!-- -->
        return weight;
    }

    public void setWeight(double weight) {<!-- -->
        this.weight = weight;
    }

    @Override
    public void accept(Visitor visitor) {<!-- -->
        visitor.visit(this);
    }
}
Visitor interface

The cashier is similar to the visitor, who visits the products selected by the user. We assume that discounts are based on the production date, and expired products cannot be sold. Note that this pricing strategy does not apply to alcohol. As a cashier, you need to apply different pricing to different products. method.

/**
 * Visitor interface - call the corresponding overloaded method according to different input parameters
 **/
public interface Visitor {<!-- -->

    public void visit(Candy candy); //Candy overload method

    public void visit(Wine wine); //Wine overload method

    public void visit(Fruit fruit); //Fruit overload method
}
Specific visitors

Create a pricing business class to perform discount pricing on three types of goods. The three overloaded methods of the discount pricing visitor implement the pricing methods of the three types of goods respectively, reflecting the polymorphism of the visit() method.

/**
 * Discounted visitor category
 **/
public class DiscountVisitor implements Visitor {<!-- -->

    private LocalDate billDate;

    public DiscountVisitor(LocalDate billDate) {<!-- -->
        this.billDate = billDate;
        System.out.println("Billing Date: " + billDate);
    }

    @Override
    public void visit(Candy candy) {<!-- -->
        System.out.println("Candy: " + candy.getName());

        // Candies older than 180 days are prohibited from being sold, otherwise all candies will be 10% off
        long days = billDate.toEpochDay() - candy.getProduceDate().toEpochDay();

        if(days > 180){<!-- -->
            System.out.println("Candy that is more than half a year old, please do not eat it!");
        }else{<!-- -->
            double realPrice = candy.getPrice() * 0.9;
            System.out.println("The discounted price of candy is: " +
                    NumberFormat.getCurrencyInstance().format(realPrice));
        }
    }

    @Override
    public void visit(Wine wine) {<!-- -->
        System.out.println("Wine: " + wine.getName() + ", no discount price!");
        System.out.println("Original price: " +
                NumberFormat.getCurrencyInstance().format(wine.getPrice()));
    }

    @Override
    public void visit(Fruit fruit) {<!-- -->
        System.out.println("Fruit: " + fruit.getName());

        long days = billDate.toEpochDay() - fruit.getProduceDate().toEpochDay();

        double rate = 0;
        if(days > 7){<!-- -->
            System.out.println("Do not eat fruits that are older than seven days!");
        }else if(days > 3){<!-- -->
            rate = 0.5;
        }else{<!-- -->
            rate = 1;
        }

        double realPrice = fruit.getPrice() * fruit.getWeight() * rate;
        System.out.println("The price of fruit is: " +
                NumberFormat.getCurrencyInstance().format(realPrice));
    }
}
public class Client {<!-- -->

    public static void main(String[] args) {<!-- -->

// Candy candy = new Candy("Dove Chocolate", LocalDate.of(2022, 1, 1), 10.0);
//
// Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,5));
// visitor.visit(candy);

        //Add 3 items to the shopping cart
// List<Product> products = Arrays.asList(
// new Candy("Golden Monkey Milk Candy",LocalDate.of(2022,10,1),10),
// new Wine("Langjiu",LocalDate.of(2022,10,1),1000),
// new Fruit("Strawberry",LocalDate.of(2022,10,8),50,1)
// );

// Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,5));
// for (Product product : products) {<!-- -->
// //visitor.visit();
// }

        //Simulate adding multiple products
        List<Acceptable> list = Arrays.asList(
                new Candy("Golden Monkey Milk Candy",LocalDate.of(2022,10,1),10),
                new Wine("Langjiu",LocalDate.of(2022,10,1),1000),
                new Fruit("Strawberry",LocalDate.of(2022,10,8),50,1)
                );

        Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
        for (Acceptable product : list) {<!-- -->
            product.accept(visitor);
        }
    }
}

Although the above code can meet the current needs, imagine this scenario: Since the visitor’s overload method can only price a specific product, if the customer selects multiple products for checkout, it may cause The dispatch problem of overloaded methods (the problem of who should calculate it).

First, we define a class Acceptable to receive visitors, which defines an accept (Visitor visitor) method, which can be accepted as long as it is a subclass of visitor.

/**
 * The interface of the receptionist (abstract element role)
 **/
public interface Acceptable {<!-- -->

    //Receive all subclasses of Visitor visitors
    public void accept(Visitor visitor);
}
/**
* Candy
* @author spikeCong
* @date 2022/10/18
**/
public class Candy extends Product implements Acceptable{<!-- -->
public Candy(String name, LocalDate producedDate,double price) {<!-- -->
super(name, producedDate, price);
}
\t//test
\t
@Override
public void accept(Visitor visitor) {<!-- -->
//Call the visitor in the accept implementation method and pass back "this" yourself. this is a clear identity, there is no generic type
visitor.visit(this);
}
}

Once the code is written to this point, you can respond to changes in pricing methods or business logic. The visitor pattern successfully separates data resources (requires the receptionist interface) from data algorithms (requires the visitor interface). The use of overloaded methods allows diverse algorithms to form their own systems, and the polymorphic visitor interface ensures that the system
In order to improve the scalability of the system algorithm, the data remains relatively fixed, and finally an algorithm class corresponding to a set of data is formed.

4 Summary of visitor patterns