博客原文地址:折腾Java设计模式之访问者模式

访问者模式

Represent an operation to be performed on the elements of an object structure. Visitor lets a new operation be defined without changing the classes of the elements on which it operates.

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

意图 主要将数据结构与数据操作分离。

主要解决 稳定的数据结构和易变的操作耦合问题。

何时使用 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。

访问者模式的主要角色

Visitor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。

ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。

ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。

ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。

使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

访问者模式相关UML图

UML类图和时序图

在类图中可以看出,ElementA实现了接口Element的accept(visitor)方法,而通过visitor.visitElementA(this),相同visitor1类通过实现visitElementA(ElementA a)方法与ElementA关联。相同的ElementB亦是如此原理。

右上角的时序图,Client对象有一组Element的数据结构,通过循环对每个元素Element调用accept(visitor)方法,例如先是ElementA调用accept(visitor),实际上就是调用visitor1的visitElementA(A),同样情况对ElementB。

更加清晰的类图如下

实例干货

跳转到对应的源码

//抽象元素
public interface Element { void accept(ElementVisitor visitor);
} //具体元素-车轮
@Data
@AllArgsConstructor
public class Wheel implements Element { private String name; @Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
} //具体元素-车身
public class Body implements Element { @Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
} //具体元素-引擎
public class Engine implements Element { @Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
} //具体元素-整车
public class Car implements Element { public void accept(final ElementVisitor visitor) {
visitor.visit(this);
} } //抽象访问者
public interface ElementVisitor { void visit(Body body); void visit(Engine engine); void visit(Wheel wheel); void visit(Car car);
} //具体的一个访问者,纯打印
@Slf4j
public class DoElementVisitor implements ElementVisitor { @Override
public void visit(Body body) {
log.info("Moving my body");
} @Override
public void visit(Engine engine) {
log.info("Starting my engine");
} @Override
public void visit(Wheel wheel) {
log.info("Kicking my " + wheel.getName() + " wheel");
} @Override
public void visit(Car car) {
log.info("Starting my car");
}
} //单独还定义对象结构,其实完全就可以使用列表就可以
@Data
public class ElementStructure { private List<Element> list = Lists.newArrayList(); public void addElement(Element element){
list.add(element);
} public void accept(ElementVisitor visitor) {
for (Element elem : list) {
elem.accept(visitor);
}
}
}

上述就是针对访问者模式做的一个对于汽车零件的一个打印效果。

public class ClientWithVisitor {

    public static void main(String[] args) {
ElementStructure structure = new ElementStructure();
structure.addElement(new Wheel("front left"));
structure.addElement(new Wheel("front right"));
structure.addElement(new Wheel("back left"));
structure.addElement(new Wheel("back right"));
structure.addElement(new Body());
structure.addElement(new Engine());
structure.addElement(new Car()); structure.accept(new DoElementVisitor());
}
} @Slf4j
public class ClientWithoutVisitor { public static void main(String[] args) {
ElementStructure structure = new ElementStructure();
structure.addElement(new Wheel("front left"));
structure.addElement(new Wheel("front right"));
structure.addElement(new Wheel("back left"));
structure.addElement(new Wheel("back right"));
structure.addElement(new Body());
structure.addElement(new Engine());
structure.addElement(new Car()); structure.getList().forEach(e -> {
if (e instanceof Body) {
log.info("Moving my body");
} else if (e instanceof Engine) {
log.info("Starting my engine");
} else if (e instanceof Car) {
log.info("Starting my car");
} else if (e instanceof Wheel) {
log.info("Kicking my " + ((Wheel)e).getName() + " wheel");
}
});
} }

打印结果都是一样的

总结分析

在上面的例子中分别用了访问者模式和非访问者模式两种方法。

1、使用VIsitor的好处一目了然,当需要修改某些元素的业务逻辑时,只需要修改Visitor类中相对应的操作函数即可。例如假设要修改Wheel的逻辑,只需要修改Visitor的visit(Wheel wheel)方法即可。

2、假设我们又需要新增一个汽车元素天窗的话,只需要在visitor中添加新的接口以处理新元素,而别的元素可以保持不动。 违背开闭原则。

3、当我们需要添加新的业务操作,只需要添加新的具体访问者,其他的依旧可以保持不变。符合开闭原则。

同样,有好处也就有缺陷,因为逻辑在visitor里面,所有visitor和Element高度耦合,同样针对visit方法返回类型,需要设计的优雅,如若不然,后期一旦修改返回类型,影响的范围就广,所有访问者接口和实现都波及到。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问,这一点跟迪米特法则和依赖倒置原则相违背。

总的而言,访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。

JDK中含有的访问者模式

提供一个方便的可维护的方式来操作一组对象。它使得你在不改变操作的对象前提下,可以修改或者扩展对象的行为。

javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor

javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor

参考

访问者模式|菜鸟教程

Visitor pattern

JAVA设计模式(23):行为型-访问者模式(Visitor)

欢迎关注我的微信公众号

折腾Java设计模式之访问者模式的更多相关文章

  1. 折腾Java设计模式之建造者模式

    博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...

  2. 折腾Java设计模式之备忘录模式

    原文地址:折腾Java设计模式之备忘录模式 备忘录模式 Without violating encapsulation, capture and externalize an object's int ...

  3. 折腾Java设计模式之状态模式

    原文地址 折腾Java设计模式之状态模式 状态模式 在状态模式(State Pattern)中,类的行为是基于它的状态改变的.这种类型的设计模式属于行为型模式.在状态模式中,我们创建表示各种状态的对象 ...

  4. 折腾Java设计模式之模板方法模式

    博客原文地址:折腾Java设计模式之模板方法模式 模板方法模式 Define the skeleton of an algorithm in an operation, deferring some ...

  5. 折腾Java设计模式之命令模式

    博客原文地址 折腾Java设计模式之命令模式 命令模式 wiki上的描述 Encapsulate a request as an object, thereby allowing for the pa ...

  6. JAVA设计模式之访问者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

  7. 折腾Java设计模式之迭代器模式

    迭代器模式 Provide a way to access the elements of an aggregate object sequentially without exposing its ...

  8. 15.java设计模式之访问者模式

    基本需求: 电脑需要键盘鼠标等固定的组件组成 现在分为个人,组织等去买电脑,而同一种组件对不同的人(访问者)做出不同的折扣,从而电脑的价格也不一样 传统的解决方法:在组件内部进行判断访问人的类型,从而 ...

  9. 由电脑专卖系统引发的Java设计模式:访问者模式

    目录 定义 意图 解决问题 何时使用 优缺点 结构 电脑专卖系统 定义 访问者模式是对象的行为型模式,它的目的是封装一些施加于某些数据结构元素之上的操作,一旦这些操作需要修改的话,接收这个操作的数据结 ...

随机推荐

  1. DBA_OBJECTS

    类型:View Owner:SYS 内容:记录了数据库中所有的对象 字段: OWNER:对象的Owner OBJECT_NAME:对象名称 SUBOBJECT_NAME:对象的子对象名字,例如分区 O ...

  2. 2.JAVA-基础语法以及String的介绍

    1.goto和const 目前java中,和C/C++有点区别,就是暂未用到goto const关键字.示例如下: public class Hello{ public static void mai ...

  3. Angular(03)-- lint风格规范和WebStorm小技巧

    在开始讲 Angular 各个核心知识点之前,想先来讲讲开发工具 WebStorm 的一些配置以及相应配置文件如 tslint.json 的配置. 因为我个人比较注重代码规范.代码风格,而对于这些规范 ...

  4. XSS Challenges xss-quiz.int21h.jp

    概述: https://xss-quiz.int21h.jp/ Stage #1 payload: <script>alert(document.domain);</script&g ...

  5. Changes of user relationship in AD can't be correctly synchronized to SCSM

    The relationship of users might be not correctly updated if related users were once re-named in AD o ...

  6. asp.net core重新加载应用配置

    asp.net core重新加载应用配置 Intro 我把配置放在了数据库或者是Redis里,配置需要修改的时候我要直接修改数据库,然后调用一个接口去重新加载应用配置,于是就尝试写一个运行时重新加载配 ...

  7. CI持续集成系列之(九)代码发布脚本模板书写

    前言 前面我们介绍了Jenkins来发布项目通过nginx来展示流程,那里只是提供了一个简单的测试脚本,接下来呢介绍一下一个比较完善的发布脚本,该脚本可实现从gitlab服务器获取代码,打包,部署到W ...

  8. C++基础——类继承

    一.前言  好吧,本系列博客已经变成了<C++ Primer Plus>的读书笔记,尴尬.在使用C语言时,多通过添加库函数的方式实现代码重用,但有一个弊端就是原来写好的代码并不完全适用于现 ...

  9. bootStrap-table服务器端后台分页的使用,以及自定义搜索框的实现,前端代码到数据查询超详细讲解

    关于分页,之前一直纯手写js代码来实现,最近又需要用到分页,找了好多最终确定bootstrap-table,正好前端页面用的是bootstrap. 首先下载BootStrap-table的js和CSS ...

  10. Linux 桌面玩家指南:01. 玩转 Linux 系统的方法论

    特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...