访问者模式
在学习访问者模式之前,我们先来看一个生活中的例子,比如我们看到的唱歌选秀节目中需要观众对歌手进行测评我们将观众分为男生和女生,在表演完之后,观众需要对歌手的表现作出自己的评价,我们假设目前的评价有成功和失败两种。
如果使用传统的方式我们可能会想着使用继承的方式来完成,但是呢,这种方式如果系统比较小,人数比较少的时候还可以使用,但是当系统越来越多的功能时,对代码改动较大,违反了ocp原则,不利于维护。扩展性也不好,基于此种情况我们引入接下来将要学习的访问者模式。
一、访问者模式基本介绍
- 访问者模式,封装一些作用于某种数据结构的各元素的操作,他可以在不改变数据结构的前提下定义作用于这些元素的新的操作
- 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同的操作。需要注意的是这些操作没有关联,同时需要避免让这些操作“污染”这些对象的类,我们可以选用访问者模式解决。
类图说明:
- Visitor是抽象访问者,可以是抽象类或者接口,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
- ConcreteVisitor:是一个具体的访问者,实现每个由Visitor声明的操作,是每个操作实现的部分,具体体现访问者访问到一个类以后该怎么干,要做什么事情。
- ObjectStructure:元素产生者,一般容纳在多个不同类、不同接口的容器,比如List、Set、Map等。能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
- Element定义一个accept方法,接收一个访问者对象,可以是接口或抽象类,声明接受哪一类访问者访问。
- ConcreteElement为具体元素,实现accept方法
二、访问者模式应用案例
-
将观众分为男生和女生,对歌手进行测评,当看完某个歌手的表演之后,得到他们对该歌手不同的评价,
-
package com.slp.pattern.visitor; /** * @ClassName Person * @Description TODO * @Author sanglp * @Date 2020/8/31 8:23 * @Version 1.0 **/ public abstract class Person { //提供一个方法,让访问者可以访问 public abstract void accept(Action action); }
package com.slp.pattern.visitor; /** * @ClassName Action * @Description TODO * @Author sanglp * @Date 2020/8/31 8:20 * @Version 1.0 **/ public abstract class Action { public abstract void getManResult(Man man); public abstract void getWomenResult(Woman woman); }
package com.slp.pattern.visitor; /** * @ClassName Man * @Description TODO * @Author sanglp * @Date 2020/8/31 8:22 * @Version 1.0 **/ public class Man extends Person{ @Override public void accept(Action action) { action.getManResult(this); } }
package com.slp.pattern.visitor; /** * @ClassName Woman * @Description 1.这里我们使用到了一个双分派,即首先在客户端程序中将具体的状态作为参数 * 传递到Woman中 * 2.然后woman这个类调用作为参数的“具体方法”中的方法getWomanResult()同时将自己作为参数传入,完成了第二次的分派 * @Author sanglp * @Date 2020/8/31 8:23 * @Version 1.0 **/ public class Woman extends Person{ @Override public void accept(Action action) { action.getWomenResult(this); } }
package com.slp.pattern.visitor; /** * @ClassName Success * @Description TODO * @Author sanglp * @Date 2020/8/31 8:24 * @Version 1.0 **/ public class Success extends Action { @Override public void getManResult(Man man) { System.out.println("男生的评价是成功"); } @Override public void getWomenResult(Woman woman) { System.out.println("女生给的评价是很成功"); } }
package com.slp.pattern.visitor; /** * @ClassName Fail * @Description TODO * @Author sanglp * @Date 2020/8/31 8:25 * @Version 1.0 **/ public class Fail extends Action { @Override public void getManResult(Man man) { System.out.println("男生给出的评价是失败"); } @Override public void getWomenResult(Woman woman) { System.out.println("女生给出的评价是失败"); } }
package com.slp.pattern.visitor; /** * @ClassName Wait * @Description TODO * @Author sanglp * @Date 2020/8/31 8:42 * @Version 1.0 **/ public class Wait extends Action { @Override public void getManResult(Man man) { System.out.println("男生给的评价是待定"); } @Override public void getWomenResult(Woman woman) { System.out.println("女生给的评价是待定"); } }
package com.slp.pattern.visitor; import java.util.LinkedList; import java.util.List; /** * @ClassName ObjectStructure * @Description 数据结构,管理很多人 * @Author sanglp * @Date 2020/8/31 8:32 * @Version 1.0 **/ public class ObjectStructure { //维护了一个集合 private List<Person> persons = new LinkedList<Person>(); //增加到list public void attach(Person person){ persons.add(person); } //移除 public void detach(Person person){ persons.remove(person); } //显示测评结果 public void display(Action action){ for (Person p:persons ) { p.accept(action); } } }
package com.slp.pattern.visitor; /** * @ClassName Client * @Description TODO * @Author sanglp * @Date 2020/8/31 8:35 * @Version 1.0 **/ public class Client { public static void main(String[] args) { //创建ObjectStructure ObjectStructure objectStructure = new ObjectStructure(); objectStructure.attach(new Man()); objectStructure.attach(new Woman()); objectStructure.attach(new Man()); Success success = new Success(); objectStructure.display(success); Fail fail = new Fail(); objectStructure.display(fail); } }
-
应用案例小结-双分派
所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接受者的类型,比如上面的例子中,我们需要增加一种评判标准,那么我们只需要增加一个Action的子类即可在客户端调用,不需要修改其他类的代码。
三、访问者模式总结
1.优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一、可以做报表、UI、拦截器与过滤器。适用于数据结构相对稳定的系统
2. 缺点
- 具体元素对访问者公开细节,也就是说访问者关注了其他类的内部细节,这是迪米特原则所不允许的,这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式是比较合适的