访问者模式
根据软件设计的开闭原则 (对修改关闭,对扩展开放),我们怎么样实现这种需求呢?
●Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
●ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
●Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。【稍后将介绍为什么要这样设计。】
●ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
● ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
案例说明:
比如有一个公园,有一到多个不同的组成部分;该公园存在多个访问者:清洁工A负责打扫公园的A部分,清洁工B负责打扫公园的B部分,公园的管理者负责检点各项事务是否完成,上级领导可以视察 公园等等。也就是说,对于同一个公园,不同的访问者有不同的行为操作,而且访问者的种类也可能需要根据时间的推移而变化(行为的扩展性)。
代码编写:
Vistor(抽象访问者)
public interface Vistor {
public void visit (Park park);
public void visit (ParkA parkA);
public void visit (ParkB parkB);
}
ConcreteVisitor(具体访问者)
/**
* @author 孙一鸣 on 2020/2/16
* 清洁工A 负责 ParkA 地区的卫生
*/
public class VisitorA implements Vistor {
@Override
public void visit(Park park) {
}
@Override
public void visit(ParkA parkA) {
System.out.println("清洁工A 负责 ParkA 地区的卫生");
}
@Override
public void visit(ParkB parkB) {
}
}
/**
* @author 孙一鸣 on 2020/2/16
* 清洁工B 负责 ParkB 地区的卫生
*/
public class VisitorB implements Vistor {
@Override
public void visit(Park park) {
}
@Override
public void visit(ParkA parkA) {
}
@Override
public void visit(ParkB parkB) {
System.out.println("清洁工B 负责 ParkB 地区的卫生 ");
}
}
/**
* @author 孙一鸣 on 2020/2/16
*/
public class VistorManager implements Vistor {
@Override
public void visit(Park park) {
System.out.println("管理员: 负责公园检查");
}
@Override
public void visit(ParkA parkA) {
System.out.println("管理员: 负责公园A检查");
}
@Override
public void visit(ParkB parkB) {
System.out.println("管理员: 负责公园B检查");
}
}
Element(抽象元素)
//它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。【稍后将介绍为什么要这样设计。】
/**
* @author 孙一鸣 on 2020/2/16
*/
public interface ParkElement {
//用来接待访问者
//它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。【稍后将介绍为什么要这样设计。】
public void accept (Vistor vistor);
}
ConcreteElement(具体元素)
/**
* 公园的A区域
*
* @author 孙一鸣 on 2020/2/16
*/
public class ParkA implements ParkElement {
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
}
/**
* 公园的B区域
*
* @author 孙一鸣 on 2020/2/16
*/
public class ParkB implements ParkElement {
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
}
在具体元素类ConcreteElementA的accept()方法中,通过调用Visitor类的visit()方法实现对元素的访问,并以当前对象作为visit()方法的参数。其具体执行过程如下:
(1) 调用具体元素类的accept(Visitor visitor)方法,并将Visitor子类对象作为其参数;
(2) 在具体元素类accept(Visitor visitor)方法内部调用传入的Visitor对象的visit()方法,如visit(ConcreteElementA elementA),将当前具体元素类对象(this)作为参数,如visitor.visit(this);
(3) 执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法。
这种调用机制也称为 “双重分派”,正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法,程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。
ObjectStructure(对象结构)
package 设计模式.访问者模式.风行;
/**
* 访问者模式中的结构体
*
* @author 孙一鸣 on 2020/2/16
*/
public class Park implements ParkElement{
private ParkA parkA;
private ParkB parkB;
public Park() {
this.parkA = new ParkA();
this.parkB = new ParkB();
}
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
parkA.accept(vistor);
parkB.accept(vistor);
}
}
双重分派:
使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法,程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。
新增加一个访问者 ----公园老板
公园老板要去视察公园 公园A 公园B
公园 公园A 公园B 不用去改变
public void accept(Vistor vistor) {
vistor.visit(this);
}
如果要在系统中增加一种新的访问者,无须修改源代码,只要增加一个新的具体访问者类即可,在该具体访问者中封装了新的操作元素对象的方法。从增加新的访问者的角度来看,访问者模式符合“开闭原则”
如果要在系统中增加一种新的具体元素,例如增加一种新的员工类型为“退休人员”,由于原有系统并未提供相应的访问接口(在抽象访问者中没有声明任何访问“退休人员”的方法),因此必须对原有系统进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法。从增加新的元素的角度来看,访问者模式违背了“开闭原则”。