24 行为型模式-访问者模式

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 访问者模式总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值