目录
1 访问者模式介绍
2 访问者模式原理
3 访问者模式实现
4 访问者模式总结
1 访问者模式介绍
访问者模式(Visitor Pattern) 的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离
2 访问者模式原理
- 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用.
- 具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法.
- 抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法(
accept
),其意义是指,每一个元素都要可以被访问者访问。 - 具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。
- 对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构.
- 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象.
3 访问者模式实现
/**
* 抽象商品父类
* @author spikeCong
* @date 2022/10/18
**/
public abstract class Product {
private String name; //商品名
private LocalDate producedDate; // 生产日期
private double price; //单品价格
public Product(String name, LocalDate producedDate, double price) {
this.name = name;
this.producedDate = producedDate;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getProducedDate() {
return producedDate;
}
public void setProducedDate(LocalDate producedDate) {
this.producedDate = producedDate;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
/**
* 糖果类
* @author spikeCong
* @date 2022/10/18
**/
public class Candy extends Product{
public Candy(String name, LocalDate producedDate, double price) {
super(name, producedDate, price);
}
}
/**
* 酒水类
* @author spikeCong
* @date 2022/10/18
**/
public class Wine extends Product{
public Wine(String name, LocalDate producedDate, double price) {
super(name, producedDate, price);
}
}
/**
* 水果类
* @author spikeCong
* @date 2022/10/18
**/
public class Fruit extends Product{
//重量
private float weight;
public Fruit(String name, LocalDate producedDate, double price, float weight) {
super(name, producedDate, price);
this.weight = weight;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
}
访问者接口
/**
* 访问者接口-根据入参不同调用对应的重载方法
* @author spikeCong
* @date 2022/10/18
**/
public interface Visitor {
public void visit(Candy candy); //糖果重载方法
public void visit(Wine wine); //酒类重载方法
public void visit(Fruit fruit); //水果重载方法
}
具体访问者
/**
* 折扣计价访问者类
* @author spikeCong
* @date 2022/10/18
**/
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());
//获取产品生产天数
long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
if(days > 180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
double rate = 0.9;
double discountPrice = candy.getPrice() * rate;
System.out.println("糖果打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
}
}
@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.getProducedDate().toEpochDay();
double rate = 0;
if(days > 7){
System.out.println("超过七天的水果,请勿食用!");
}else if(days > 3){
rate = 0.5;
}else{
rate = 1;
}
double discountPrice = fruit.getPrice() * fruit.getWeight() * rate;
System.out.println("水果价格: "+NumberFormat.getCurrencyInstance().format(discountPrice));
}
public static void main(String[] args) {
LocalDate billDate = LocalDate.now();
Candy candy = new Candy("徐福记",LocalDate.of(2022,10,1),10.0);
System.out.println("糖果: " + candy.getName());
double rate = 0.0;
long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
System.out.println(days);
if(days > 180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
rate = 0.9;
double discountPrice = candy.getPrice() * rate;
System.out.println("打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
}
}
}
客户端
public class Client {
public static void main(String[] args) {
//德芙巧克力,生产日期2002-5-1 ,原价 10元
Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
visitor.visit(candy);
}
}
由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).
/**
* 接待者接口(抽象元素角色)
* @author spikeCong
* @date 2022/10/18
**/
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);
}
}
//酒水与水果类同样实现Acceptable接口,重写accept方法
测试
public class Client {
public static void main(String[] args) {
// //德芙巧克力,生产日期2002-5-1 ,原价 10元
Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
visitor.visit(candy);
//模拟添加多个商品的操作
List<Acceptable> products = Arrays.asList(
new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00),
new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00),
new Fruit("草莓",LocalDate.of(2022,10,12),50.00,1)
);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17));
for (Acceptable product : products) {
product.accept(visitor);
}
}
}
4 访问者模式总结
1) 访问者模式优点:
-
扩展性好
在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
-
复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
-
分离无关行为
通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
2) 访问者模式缺点:
-
对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
-
违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类。
3) 使用场景
-
当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。
-
需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。
-
需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。