背景
现实生活中有这样的场景,一个集合对象中存在多种不同的元素,并且每种元素也存在多种不同的访问者和访问方式。如超市中有多种不同的商品,存在多个顾客在买衣服。不同顾客对不同的商品评价也不一样。
当遇到被处理的数据元素相对稳定并且访问方式多种多样的数据结构,我们就可以使用访问者模式。通过访问者模式,我们可以将访问方法从数据结构中分离出来,这样我们可以扩展新的访问方法,不用修改原程序代码就能实现灵活的扩展。
什么是访问者模式
上面的文字你可能云里雾里,现在你只要知道使用访问者模式的作用是将数据结构中的元素与操作方法分离。
“
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)
”
访问者模式主要由下面5个元素组成:
“抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。”
结构图如下:
![635eeaacf04470881747460e52a51dd1.png](https://img-blog.csdnimg.cn/img_convert/635eeaacf04470881747460e52a51dd1.png)
访问者模式
代码实现
Element
public interface Element { void accept(Visitor visitor);}
ConcreteElement
public class ConcreteElementA implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } public String operationA() { return "具体元素A的操作。"; }}
public class ConcreteElementB implements Element { @Override public void accept(Visitor visitor) { visitor.visit(this); } public String operationB() { return "具体元素B的操作。"; }}
Visitor
public interface Visitor { void visit(ConcreteElementA element); void visit(ConcreteElementB element);}
ConcreteVisitor
public class ConcreteVisitorA implements Visitor { @Override public void visit(ConcreteElementA element) { System.out.println("具体访问者A访问-->" + element.operationA()); } @Override public void visit(ConcreteElementB element) { System.out.println("具体访问者A访问-->" + element.operationB()); }}
public class ConcreteVisitorB implements Visitor { @Override public void visit(ConcreteElementA element) { System.out.println("具体访问者B访问-->" + element.operationA()); } @Override public void visit(ConcreteElementB element) { System.out.println("具体访问者B访问-->" + element.operationB()); }}
ObjectStructure
public class ObjectStructure { private List list = new ArrayList<>(); public void accept(Visitor visitor) { Iterator i = list.iterator(); while (i.hasNext()) { i.next().accept(visitor); } } public void add(Element element) { list.add(element); } public void remove(Element element) { list.remove(element); }}
测试代码
@Testpublic void test() { ObjectStructure os=new ObjectStructure(); os.add(new ConcreteElementA()); os.add(new ConcreteElementB()); Visitor visitor=new ConcreteVisitorA(); os.accept(visitor); System.out.println("------------------------"); visitor=new ConcreteVisitorB(); os.accept(visitor);}
测试结果:
具体访问者A访问-->具体元素A的操作。具体访问者A访问-->具体元素B的操作。------------------------具体访问者B访问-->具体元素A的操作。具体访问者B访问-->具体元素B的操作。
调用链如下:
//举例A:ObjectStructure#accept() -> ConcreteElementA#accept() -> ConcreteVisitorA#visit()
关于访问者模式的思考
那么我们什么时候使用访问者模式呢?
当对象的结构稳定,操作算法经常会变化的时候,我们为了避免让这些操作算法的变化影响数据结构,就可以使用访问者模式,我们将算法与数据结构分离。
其实我们在使用迭代器遍历集合元素的时候,除了使用迭代器模式,也使用了访问者模式。集合本身不参与遍历,而是通过迭代器的方法去循环获取。
“
在这种地方你一定要考虑使用访问者模式:业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数据(当然了,如果你使用instanceof,那么能访问所有的数据,这没有争论),而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。
”
访问者模式是一种集中规整模式,特别适用于大规模重构的项目,在这一个阶段需求已经非常清晰,原系统的功能点也已经明确,通过访问者模式可以很容易把一些功能进行梳理,达到最终目的——功能集中化,如一个统一的报表运算、UI展现等,我们还可以与其他模式混编建立一套自己的过滤器或者拦截器。