访问者模式
简介
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作
。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
意图:主要将数据结构与数据操作分离。
主要解决:稳定的数据结构和易变的操作耦合问题。
何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
角色
Visitor(抽象访问者)
:抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
ConcreteVisitor(具体访问者)
:具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
Element(抽象元素)
:抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
ConcreteElement(具体元素)
:具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
ObjectStructure(对象结构)
:对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
代码
一组对象Archer(弓箭手) Rider(骑兵) Gunner(火枪手) 这一组对象, 由不同的访问者(AttackVisitor 和 RetreatVisitor)访问, 产生的结果会不同. 一种是进攻, 一种是撤退.
本例子中的类关系图如下所示:
Soldier是士兵接口. 本例子中实现了三种"士兵".
本例子中有两个visitor. 分别用来处理
加上依赖关系后如下图所示:
代码结构
Soldier接口
public interface Soldier {
void accept(Visitor v);
}
Soldier接口实现类
public class Archer implements Soldier {
@Override
public void accept(Visitor v) {
v.visit(this);
}
public String getArcher() {
return "Archer";
}
}
public class Gunner implements Soldier {
@Override
public void accept(Visitor v) {
v.visit(this);
}
public String getGunner() {
return "Gunner";
}
}
public class Rider implements Soldier {
@Override
public void accept(Visitor v) {
v.visit(this);
}
public String getRider() {
return "Rider";
}
}
Visitor接口
public interface Visitor {
void visit(Gunner gunner);
void visit(Rider rider);
void visit(Archer archer);
}
Visitor接口实现类
public class AttackVisitor implements Visitor {
@Override
public void visit(Gunner gunner) {
System.out.println(gunner.getGunner() + " 发起进攻");
}
@Override
public void visit(Rider rider) {
System.out.println(rider.getRider() + " 发起进攻");
}
@Override
public void visit(Archer archer) {
System.out.println(archer.getArcher() + " 发起进攻");
}
}
public class RetreatVisitor implements Visitor {
@Override
public void visit(Gunner gunner) {
System.out.println(gunner.getGunner() + " 开始撤退");
}
@Override
public void visit(Rider rider) {
System.out.println(rider.getRider() + " 开始撤退");
}
@Override
public void visit(Archer archer) {
System.out.println(archer.getArcher() + " 开始撤退");
}
}
测试类
public class ApiTest {
@Test
public void testCommand(){
Soldier[] list = {new Gunner(), new Rider(), new Archer()};
RetreatVisitor retreat = new RetreatVisitor();
AttackVisitor attack = new AttackVisitor();
for (Soldier element : list) {
element.accept(attack);
}
System.out.println();
for (Soldier element : list) {
element.accept(retreat);
}
}
}
总结
1、使用VIsitor的好处一目了然,当需要修改某些元素的业务逻辑时,只需要修改Visitor类中相对应的操作函数即可
。例如假设要修改Wheel的逻辑,只需要修改Visitor的visit(Wheel wheel)方法即可。
2、假设我们又需要新增一个汽车元素天窗的话,只需要在visitor中添加新的接口以处理新元素,而别的元素可以保持不动。 违背开闭原则。
3、当我们需要添加新的业务操作,只需要添加新的具体访问者,其他的依旧可以保持不变。符合开闭原则。
同样,有好处也就有缺陷,因为逻辑在visitor里面,所有visitor和Element高度耦合,同样针对visit方法返回类型,需要设计的优雅,如若不然,后期一旦修改返回类型,影响的范围就广,所有访问者接口和实现都波及到。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问,这一点跟迪米特法则和依赖倒置原则相违背。
总的而言,访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。