1 访问者模式介绍
访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式。
2 访问者模式原理
3 访问者模式实现
我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类.
/**
* 抽象商品父类
**/
public abstract class Product {
private String name; //商品名
private LocalDate produceDate; //生产日期
private double 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;
}
}
/**
* 糖果类
**/
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) {
//在accept方法中调用访问者, 并将自己 this 传递回去.
visitor.visit(this);
}
}
/**
* 酒水类
**/
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);
}
}
/**
* 水果类
**/
public class Fruit extends Product implements Acceptable{
private double 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);
}
}
访问者接口
收银员就类似于访问者,访问用户选择的商品,我们假设根据生产日期进行打折,过期商品不能够出售. 注意这种计价策略不适用于酒类,作为收银员要对不同商品应用不同的计价方法。
/**
* 访问者接口 - 根据入参的不同调用对应的重载方法
**/
public interface Visitor {
public void visit(Candy candy); //糖果重载方法
public void visit(Wine wine); //酒类重载方法
public void visit(Fruit fruit); //水果重载方法
}
具体访问者
创建计价业务类,对三类商品进行折扣计价,折扣计价访问者的三个重载方法分别实现了3类商品的计价方法,体现了visit() 方法的多态性.
/**
* 折扣计价访问者类
**/
public class DiscountVisitor implements Visitor {
private LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期: " + billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("糖果: " + candy.getName());
//糖果大于180天,禁止售卖,否则糖果一律九折
long days = billDate.toEpochDay() - candy.getProduceDate().toEpochDay();
if(days > 180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
double realPrice = candy.getPrice() * 0.9;
System.out.println("糖果打折后的价格为: " +
NumberFormat.getCurrencyInstance().format(realPrice));
}
}
@Override
public void visit(Wine wine) {
System.out.println("酒类: " + wine.getName() +",无折扣价格!");
System.out.println("原价售卖: " +
NumberFormat.getCurrencyInstance().format(wine.getPrice()));
}
@Override
public void visit(Fruit fruit) {
System.out.println("水果: " + fruit.getName());
long days = billDate.toEpochDay() - fruit.getProduceDate().toEpochDay();
double rate = 0;
if(days > 7){
System.out.println("超过七天的水果,请勿食用!");
}else if(days > 3){
rate = 0.5;
}else{
rate = 1;
}
double realPrice = fruit.getPrice() * fruit.getWeight() * rate;
System.out.println("水果价格为: " +
NumberFormat.getCurrencyInstance().format(realPrice));
}
}
public class Client {
public static void main(String[] args) {
// Candy candy = new Candy("德芙巧克力", LocalDate.of(2022, 1, 1), 10.0);
//
// Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,5));
// visitor.visit(candy);
//将3件商品加入购物车
// List<Product> products = Arrays.asList(
// new Candy("金丝猴奶糖",LocalDate.of(2022,10,1),10),
// new Wine("郎酒",LocalDate.of(2022,10,1),1000),
// new Fruit("草莓",LocalDate.of(2022,10,8),50,1)
// );
// Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,5));
// for (Product product : products) {
// //visitor.visit();
// }
//模拟添加多个商品
List<Acceptable> list = Arrays.asList(
new Candy("金丝猴奶糖",LocalDate.of(2022,10,1),10),
new Wine("郎酒",LocalDate.of(2022,10,1),1000),
new Fruit("草莓",LocalDate.of(2022,10,8),50,1)
);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
for (Acceptable product : list) {
product.accept(visitor);
}
}
}
上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).
首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitor visitor)方法, 只要是visitor的子类都可以接收.
/**
* 接待者这接口 (抽象元素角色)
**/
public interface Acceptable {
//接收所有的Visitor访问者的子类
public void accept(Visitor visitor);
}
/**
* 糖果类
* @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);
}
//测试
@Override
public void accept(Visitor visitor) {
//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型
visitor.visit(this);
}
}
代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系
统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。
4 访问者模式总结