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);}@Overridepublic 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);}@Overridepublic 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;}@Overridepublic 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);}@Overridepublic 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));}}@Overridepublic void visit(Wine wine) {System.out.println("酒类: " + wine.getName() +",无折扣价格!");System.out.println("原价售卖: " +NumberFormat.getCurrencyInstance().format(wine.getPrice()));}@Overridepublic 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);}//测试@Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}
代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系
统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。
4 访问者模式总结