设计模式(23):访问者模式

「尺有所短,寸有所长;不忘初心,方得始终。」

一、访问者模式是什么

在我们日常生活中,经常会见到不同的角色针对相同的事务有着不同的处理方式,比如

  1. 树木在造纸厂中是纸的原材料,在建筑行业是建筑材料,
  2. 汽车在4s店是商品,在自己手中是交通工具

这些其实都是访问者模式的类比引用,通过不同处理方式为数据结构中的每个元素提供多种访问方式

  • 【定义】

    将作用于某种数据结构中的各元素的操作分离出来封装成独立的访问者类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作, 为数据结构中的每个元素提供多种访问方式。

  • 【本质】

    访问者模式是一种「将数据操作和数据结构分离」设计模式,属于行为型模式,「是行为型模式中最复杂的一种模式」

二、访问者模式的适用场景

  • 【适用场景】
    • 对象的数据结构相对稳定,但对其的操作算法经常变化,可使用访问者模式。
    • 对象结构中的对象需要「提供多种不同且不相关的行为」,而且「行为变化不影响对象的结构」,可使用访问者模式。
    • 当某个行为仅在类层次结构中的某一些类中有意义时, 可使用访问者模式。
  • 【生活案例】
    • 树木被造纸厂用来造纸的原材料,被建筑行业当成建筑材料,
    • 汽车被4s店当成商品,被人们当成交通工具
    • 水在水电厂被用来发电,在生活中用来饮用

三、访问者模式结构

  • 「抽象访问者(Visitor)角色」

    定义一个访问具体元素的接口 visit(),该接口中的「参数类型标识了被访问的具体元素」

  • 「具体访问者(ConcreteVisitor)角色」

    实现抽象访问者接口,不同的具体访问者实现不同行为。

  • 「抽象元素(Element)角色」

    声明一个包含「接受访问者」操作的接口 accept(),被接受的访问者对象作为 accept() 方法的参数。

  • 「具体元素(ConcreteElement)角色」

    实现抽象元素角色提供的 accept() 操作,根据当前元素类将其调用重定向到相应访问者的方法。

    accept()的方法体通常都是 visitor.visit(this) ,也可以包含本身业务逻辑的相关操作。

  • 「对象结构(Object Structure)角色」

    一个包含元素角色的容器「提供让访问者对象遍历容器中的所有元素的方法」,通常由 List、Set、Map 等聚合类实现。

  • 「客户端(Client)角色」

    创建对象结构角色,通过抽象接口与集合中的对象进行交互。

设计模式(23):访问者模式

四、访问者模式实现方式

  • 定义一个访问者接口,声明【访问】方法, 具体元素类作为方法参数。
  • 定义一个 具体访问者,实现访问者接口的【访问】方法。
  • 定义抽象元素接口,声明【接受访问者】方法,具体访问者对象作为方法参数。
  • 定义具体元素,实现抽象元素接口的【接受访问者】方法。「必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。」
  • 定义对象结构类,提供让访问者对象遍历容器中的所有元素的方法。
  • 客户端创建对象结构类,并初始化具体元素至对象结构类中。

五、访问者模式的实现

【案例】:建材公司,造纸厂对树的不同处理

【案例说明】:在此案例中「树」属于一个抽象元素,它有被加工的方法,而建材公司,造纸厂则是访问者,对抽象元素有不同的行为。

  • 「抽象访问者(Visitor)角色」

    /**
    * 抽象访问者(Visitor)角色
    * @author Edwin
    * @date 2021/11/23 11:17
    */

    public interface Visitor {
    /**
    * 定义一个访问具体元素的接口 visit(),
    * 接口中的Visitor参数类型标识了被访问的具体元素。
    * 此案例中作为树的加工方法
    * @author Edwin
    * @date 2021/11/23 11:17
    */

    void visit(Element element);
    /**
    * 定义一个访问具体元素的接口 visit(),
    * 接口中的Visitor参数类型标识了被访问的具体元素。
    * 此案例中作为机器的调用方法
    * @author Edwin
    * @date 2021/11/23 11:17
    */

    void processing(Element element);
    }
  • 「具体访问者(ConcreteVisitor)角色」

    /**
    * 具体访问者(ConcreteVisitor)角色 造纸厂
    * @author Edwin
    * @date 2021/11/23 17:39
    */

    public class PaperMillVisitor implements Visitor{
    @Override
    public void visit(Element element) {
    System.out.println("造纸厂使用树造了各种各样的纸");
    }

    @Override
    public void processing(Element element) {
    System.out.println("造纸厂使用机器加工了树;");
    }
    }

    /**
    * 具体访问者(ConcreteVisitor)角色 建材公司
    * @author Edwin
    * @date 2021/11/23 17:39
    */

    public class BuildingMaterialsVisitor implements Visitor{
    @Override
    public void visit(Element element) {
    System.out.println("建材公司使用树建造家具");
    }
    @Override
    public void processing(Element element) {
    System.out.println("建材公司使用机器加工了树;");
    }
    }
  • 「抽象元素(Element)角色」

    /**
    * 抽象元素(Element)角色
    * @author Edwin
    * @date 2021/11/23 17:33
    */

    public interface Element {
    /**
    * 接受访问者操作的接口
    * @author Edwin
    * @date 2021/11/23 17:35
    */

    void accept(Visitor visitor);
    }
  • 「具体元素(ConcreteElement)角色」

    /**
    * 具体元素(ConcreteElement)角色 :加工树的机器
    * @author Edwin
    * @date 2021/11/23 17:31
    */

    public class MachineElement implements Element{

    @Override
    public void accept(Visitor visitor) {
    //调用机器的访问方法
    visitor.processing(this);
    }
    }

    /**
    * 具体元素(ConcreteElement)角色 :树
    * @author Edwin
    * @date 2021/11/23 17:31
    */

    public class TreeElement implements Element{
    /**
    * 实现accept(),重定向到相应访问者的方法。
    * @author Edwin
    * @date 2021/11/23 17:36
    */

    @Override
    public void accept(Visitor visitor) {
    //调用树的访问方法
    visitor.visit(this);
    }
    }
  • 「对象结构(Object Structure)角色」

    /**
    * 对象结构(Object Structure)角色
    * @author Edwin
    * @date 2021/11/23 17:47
    */

    public class ObjectStructure {
    /**
    * 持有一组元素,一个包含元素角色的容器
    * @author Edwin
    * @date 2021/11/23 19:48
    */

    private List<Element> list;
    public ObjectStructure() {
    this.list = new ArrayList<>();
    }
    /**
    * 提供让访问者对象遍历容器中的所有元素的方法
    * @author Edwin
    * @date 2021/11/23 19:49
    */

    public void accept(Visitor visitor) {
    Iterator<Element> i = list.iterator();
    while (i.hasNext()) {
    i.next().accept(visitor);
    }
    }
    /**
    * 容器中添加元素角色
    * @author Edwin
    * @date 2021/11/23 19:49
    */

    public void add(Element element) {
    list.add(element);
    }
    /**
    * 容器中添加删除角色
    * @author Edwin
    * @date 2021/11/23 19:49
    */

    public void remove(Element element) {
    list.remove(element);
    }
    }
  • 「客户端代码实现」

    public static void main(String[] args) {
    //初始化对象结构角色
    ObjectStructure structure = new ObjectStructure();
    //为对象结构添加具体元素信息
    structure.add(new TreeElement());
    structure.add(new MachineElement());
    //具体元素 造纸厂
    PaperMillVisitor paperMillVisitor = new PaperMillVisitor();
    structure.accept(paperMillVisitor);
    //具体元素 建材公司
    BuildingMaterialsVisitor buildingMaterialsVisitor = new BuildingMaterialsVisitor();
    structure.accept(buildingMaterialsVisitor);
    }
  • 「案例输出结果」

设计模式(23):访问者模式

六、访问者模式的优缺点

  • 「优点」

    • 扩展性强。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
    • 符合「单一职责原则」。访问者把相关的行为封装在一起,使每一个访问者的功能都比较单一。
    • 灵活性好。访问者模式将数据结构与行为操作解耦,「使得数据操作和数据结构分离」
  • 「缺点」

    • 违反了「迪米特原则」,破坏封装性,具体元素对访问者公布细节。
    • 具体元素变更比较困难,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作。
    • 违反了「依赖倒置原则」。访问者模式依赖了具体类,而没有依赖抽象类。

七、访问者模式和其他模式的区别

  • 可以将【访问者模式】当成【命令模式】的加强版本, 以命令的形式对不同类的多种对象执行操作。

  • 可以同时使用【访问者模式】和【迭代器模式】来遍历复杂数据结构, 并对其中的元素执行所需操作。

  • 可以使用【访问者模式】对整个【组合模式】树执行操作。

    访问者模式中的「元素对象」可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就会用到【组合模式】。

设计模式(23):访问者模式

八、总结

访问者模式的核心主要是「将数据操作行为和数据结构分离」,在使用该模式之前我们要保证「对象结构是否足够稳定,是否需要经常定义新的操作。」


原文始发于微信公众号(星河之码):设计模式(23):访问者模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/27074.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!