设计模式-行为型模式-访问者模式

访问者模式难以实现,且应用该模式可能会导致代码可读性变差,可维护性变差,除非必要,不建议使用;

1.访问者模式定义

允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离;

访问者模式主要解决的是数据与算法的耦合问题,尤其是在数据结构比较稳定,而算法多变的情况下,为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展;

1.1 访问者模式优缺点

优点

  • 扩展性好,在不修改对象结构中元素的情况下,为对象结构中的元素添加新的功能;
  • 复用性好,通过访问者来定义整个对象结构通用的功能,从而提高复用程度;
  • 分离无关行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一;

缺点

  • 对象结构变化很困难,在访问者模式中,没增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则;
  • 违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类;

1.2 访问者模式的使用场景

  • 当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。
  • 需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。
  • 需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。

2.访问者模式原理

  • 抽象访问者(Visitor):可以是接口或抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素作为入参来确定哪个重载方法被调用;
  • 具体访问者(ConcreteVisitor):实现了抽象访问者,可以有多个,每个访问者都需要实现所有数据元素类型的访问重载发方法;
  • 抽象元素类(Element):被访问的数据元素接口,定义了一个接受访问者的方法,每个元素都要可以被访问者访问;
  • 具体元素类(ConcreteElement):具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法;
  • 对象结构类(ObjectStructure):包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构;
  • 客户端:使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象;

3.访问者模式的实现

【实例】

以超市购物为例,假设超市中的两类商品:糖果、酒水进行售卖。我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计;

【代码】

访问者接口

java 复制代码
public interface Visitor {
    public void visit(Wine wine);  //酒类重载方法
    public void visit(Candy candy);  //糖果重载方法
}

接待者接口

java 复制代码
public interface Acceptable {
    //接收所有的Visitor访问者的子类实现类
    public void accept(Visitor visitor);
}

糖果类(酒类类似)

java 复制代码
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);
    }
}

具体访问者------折扣计价访问者

java 复制代码
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()));
    }
}

客户端

java 复制代码
public class Client {
    public static void main(String[] args) {
        //模拟添加多个商品的操作
        List<Acceptable> products = Arrays.asList(
                new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00),
                new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00)
        );
        Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17));
        for (Acceptable product : products) {
            product.accept(visitor);
        }
    }
}
相关推荐
晨米酱16 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机21 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式