0.参考资料
1.概述
- 封装一些作用于某种数据结构的各元素的操作,使得可以在
不改变各元素的类的前提下(稳定), 定义/扩展作用于这
些元素的新操作(变化)
—《设计模式》GoF - 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者 的接口
1.1动机
- 在软件构建过程中,由于需求的改变,某些类层次结构中常常需
要增加新的行为(方法), 如果直接在基类中做这样的更改,将会 给子类带来很繁重的变更负担,甚至破坏原有设计。
- 如何在不更改类层次结构的前提下,在运行时根据需要透明地为
1.2结构
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630490984513-de81baa9-a0a6-440d-adb7-221d9147bb41.png#clientId=ua2ff7fc6-60a7-4&from=paste&height=337&id=udff3b129&margin=%5Bobject%20Object%5D&name=image.png&originHeight=673&originWidth=966&originalType=binary&ratio=1&size=234835&status=done&style=none&taskId=ua1ad6156-ad9d-4529-8a8b-6d4f956c544&width=483)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630496102620-8a0bbd8d-3b3f-44eb-a1df-5fe75078e303.png#clientId=ua2ff7fc6-60a7-4&from=paste&height=332&id=u6b3ab193&margin=%5Bobject%20Object%5D&name=image.png&originHeight=443&originWidth=632&originalType=binary&ratio=1&size=22705&status=done&style=none&taskId=u207c674c-a439-41fc-955a-e969e0319b7&width=474)
- 角色及职责:
- Visitor 是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一种visit操作
- ConcreteVisitor :是一个具体的访问者, 实现每个有Visitor 声明的操作,是每个操作实现的部分.
-
ObjectStructure 能枚举它的元素, 可以提供一个高层的接口,用来允许访问者访问元素
- Element 定义一个accept 方法,接收一个访问者对象
- ConcreteElement 为具体元素,实现了accept 方法
2.要点总结
宏观架构
1. Visitor模式通过所谓双重分发( double dispatch )来实现在不更
改(不添加新的操作-编译时), Element类层次结构的前提下在, 运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)
1. 所谓双重分发即Visitor模式中间包括了两个**多态分发**, 第一个为accept方法的多态辨析;
第二个为VisitElementX方法的多态辨析。
1. Visitor模式的最大缺点在于扩展类层次结构.(增添新的Element子
类)会导致Visitor类的改变。因此Vistor模式适用于”Element 类层次结构稳定,而其中的操作却经常面临质繁改动“
微观代码
- 优点
1. 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
1. 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据
结构相对稳定的系统
- 缺点
1. 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米
特法则所不建议的, 这样造成了具体元素变更比较困难
1. 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 总结: 若一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问
者模式就是比较合适的
3.案例
需求
- 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对
该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等)
传统方式硬编码分析
- 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改
动较大,违反了ocp原则, 不利于维护
-
扩展性不好,比如 增加了新的观众类型,或者管理方法(评分类型),都不好做
4.使用模式
方案
类图
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630496236859-aed753fb-4838-4fec-aa16-ce5a00025e8e.png#clientId=ua2ff7fc6-60a7-4&from=paste&height=295&id=u038e87f9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=590&originWidth=1153&originalType=binary&ratio=1&size=378536&status=done&style=none&taskId=u0ec78db2-5806-47ae-9680-ee54346ce7c&width=576.5)
代码
- 访问者: 对于每一种被访问类, 都需要一个方法以适配
// 抽象基类
public abstract class Action {
//得到男性 的测评
public abstract void getManResult(Man man);
//得到女的 测评
public abstract void getWomanResult(Woman woman);
}
// 扩展的实现类
public class Wait extends Action {
@Override
public void getManResult(Man man) {
// TODO Auto-generated method stub
System.out.println(" 男人给的评价是该歌手待定 ..");
}
@Override
public void getWomanResult(Woman woman) {
// TODO Auto-generated method stub
System.out.println(" 女人给的评价是该歌手待定 ..");
}
}
// 实现类
public class Success extends Action {
@Override
public void getManResult(Man man) {
// TODO Auto-generated method stub
System.out.println(" 男人给的评价该歌手很成功 !");
}
@Override
public void getWomanResult(Woman woman) {
// TODO Auto-generated method stub
System.out.println(" 女人给的评价该歌手很成功 !");
}
}
public class Fail extends Action {
@Override
public void getManResult(Man man) {
// TODO Auto-generated method stub
System.out.println(" 男人给的评价该歌手失败 !");
}
@Override
public void getWomanResult(Woman woman) {
// TODO Auto-generated method stub
System.out.println(" 女人给的评价该歌手失败 !");
}
}
- 元素类 (被访问者) 该部分,基于该模式应该是稳定的. 即: 扩展的子类是固定的
public abstract class Person {
//提供一个方法,让访问者可以访问
public abstract void accept(Action action);
}
//说明
//1. 这里我们使用到了双分派, 即首先在客户端程序中,将具体状态作为参数传递Woman中(第一次分派)
//2. 然后Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数
// 传入,完成第二次的分派
public class Woman extends Person{
@Override
public void accept(Action action) {
// TODO Auto-generated method stub
action.getWomanResult(this);
}
}
public class Man extends Person {
@Override
public void accept(Action action) {
// TODO Auto-generated method stub
action.getManResult(this);
}
}
- 对象结构类: ObjectStructure
//数据结构,管理很多人(Man , Woman)
public class ObjectStructure {
//维护了一个集合
private List<Person> persons = new LinkedList<>();
//增加到list
public void attach(Person p) {
persons.add(p);
}
//移除
public void detach(Person p) {
persons.remove(p);
}
//显示测评情况
public void display(Action action) {
for(Person p: persons) {
p.accept(action);
}
}
}
- 测试
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建ObjectStructure
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//成功
Success success = new Success();
objectStructure.display(success);
System.out.println("===============");
Fail fail = new Fail();
objectStructure.display(fail);
System.out.println("=======给的是待定的测评========");
Wait wait = new Wait();
objectStructure.display(wait);
}
}
男人给的评价该歌手很成功!
女人给的评价该歌手很成功!
===============
男人给的评价该歌手失败!
女人给的评价该歌手失败!
=======给的是待定的测评========
男人给的评价是该歌手待定..
女人给的评价是该歌手待定..
分析
- 扩展性: 若要添加一个Wait的状态类,考察Man类和Woman类的反 应,由于使用了双分派,**只需增加一个Action子类即可在客户端调用即可,不 需要改动任何其他类的代码**
双分派机制(Double Dispatch)
- 指不管类怎么变化,我们都能找到期望的方法运行。
双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
- 案例中的双分派: 即首先在客户端程序中,将具体状态作为参数传递Woman中(第一次分派)
然后Woman 类调用作为参数的访问者的 “具体方法” :getWomanResult(Element e). 其中将自己(this)作为参数 传入,完成第二次的分派