折腾Java设计模式之访问者模式
博客原文地址:折腾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
参考
JAVA设计模式(23):行为型-访问者模式(Visitor)
欢迎关注我的微信公众号
折腾Java设计模式之访问者模式的更多相关文章
- 折腾Java设计模式之建造者模式
博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...
- 折腾Java设计模式之备忘录模式
原文地址:折腾Java设计模式之备忘录模式 备忘录模式 Without violating encapsulation, capture and externalize an object's int ...
- 折腾Java设计模式之状态模式
原文地址 折腾Java设计模式之状态模式 状态模式 在状态模式(State Pattern)中,类的行为是基于它的状态改变的.这种类型的设计模式属于行为型模式.在状态模式中,我们创建表示各种状态的对象 ...
- 折腾Java设计模式之模板方法模式
博客原文地址:折腾Java设计模式之模板方法模式 模板方法模式 Define the skeleton of an algorithm in an operation, deferring some ...
- 折腾Java设计模式之命令模式
博客原文地址 折腾Java设计模式之命令模式 命令模式 wiki上的描述 Encapsulate a request as an object, thereby allowing for the pa ...
- JAVA设计模式之访问者模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...
- 折腾Java设计模式之迭代器模式
迭代器模式 Provide a way to access the elements of an aggregate object sequentially without exposing its ...
- 15.java设计模式之访问者模式
基本需求: 电脑需要键盘鼠标等固定的组件组成 现在分为个人,组织等去买电脑,而同一种组件对不同的人(访问者)做出不同的折扣,从而电脑的价格也不一样 传统的解决方法:在组件内部进行判断访问人的类型,从而 ...
- 由电脑专卖系统引发的Java设计模式:访问者模式
目录 定义 意图 解决问题 何时使用 优缺点 结构 电脑专卖系统 定义 访问者模式是对象的行为型模式,它的目的是封装一些施加于某些数据结构元素之上的操作,一旦这些操作需要修改的话,接收这个操作的数据结 ...
随机推荐
- java游戏开发杂谈 - 画布和画笔
在Eclipse里,编写如下两个类: package game2; import java.awt.Color; import java.awt.Graphics; import javax.swin ...
- Spring Boot入门(二):使用Profile实现多环境配置管理&如何获取配置文件值
在上一篇博客Spring Boot入门(一):使用IDEA创建Spring Boot项目并使用yaml配置文件中,我们新建了一个最原始的Spring Boot项目,并使用了更为流行的yaml配置文件. ...
- 学习python的第二天
4.26自我总结 一.程序语言 1.机械语言 由于0和1组成 优点:执行效率快 缺点:操作麻烦繁琐 2.汇编语言 比机械语言好点 优点:比机械语言操作方便 缺点,执行慢 3.高级语言 主要两个,jav ...
- ssm(Spring、Springmvc、Mybatis)实战之淘淘商城-第一天
文章大纲 一.课程介绍二.淘淘商城基本介绍三.后台管理系统工程结构与搭建四.svn代码管理五.项目源码与资料下载六.参考文章 一.课程介绍 1. 课程大纲 一共14天课程(1)第一天:电商行业的背 ...
- SpringBoot当中如何整合mybatis和注入
[学习笔记] 6.整合mybatis和注入: 马克-to-win@马克java社区: 根据第3部分的helloworld例子,用那个项目做底子.pom.xml只需要加入mybatis和mysql的部分 ...
- 关于php-fpm比较重要的几个参数
pm.max_children 设置多大合适? php-fpm.conf有两个至关重要的参数: 一个是”max_children”,另一个是”request_terminate_timeout”. p ...
- AI2(App Inventor 2)离线版服务器网络版
个人修改包括: 1.后台增加用户批量添加功能 https://gte.fsyz.net/node/1877 2.上传文件限制改为100M ,编译文件限制改为10M https ...
- [ gczdac ] 20190213 开博客啦!
测试一下! 今天开了新博客! 还自己改了下首页! 开心!!!!! 访者必阅 https://www.cnblogs.com/gczdac/ https://blog.csdn.net/qq_43540 ...
- Ambari REST API 使用介绍 - How To: Use Swagger with Ambari (Explore Ambari REST)
How To: Use Swagger with Ambari (Explore Ambari REST) Article Note : This feature is available from ...
- Java Thread.join的作用和原理
很多人对Thread.join的作用以及实现了解得很少,毕竟这个api我们很少使用.这篇文章仍然会结合使用及原理进行深度分析 内容导航 Thread.join的作用 Thread.join的实现原理 ...