动机
- 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
- 如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
模式定义
表示一个作用与某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
备注:
访问者是把操作数据结构的行为和数据拆分;前提是数据结构稳定不变,无法预知以后还有多少种类型的访问。
用到了双重分发(double dispatch),分别是数据结构中的数据元素分派和元素中accept方法中的的访问者分派;具体位置,可以看下面的实例代码。
类图
总结
核心,数据结构和数据访问分离,数据结构固定不变,数据访问对象可以动态的添加,主要是访问者可以方便的扩展。
- Visitor模式通过所谓双重分发(double dispatch)来实现在不更改(不添加新的操作-编译时操作而不是运行时操作)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
- 所谓双重分发即Visitor模式中间包括两个多态分发:第一个为不同的数据元素的多态辨析;第二个为不同的visitor的多态辨析。
- Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变(因为visitor要实现对每个元素的访问)。因此Vistor模式适用于“Element类层次结构稳定,而其中对element元素的操作却经常面临频繁改动”。
实例
场景
公司的账本,拥有固定的两种账目类型,收入和支出;但是可能会有不同的访问者,老板、财务等;
- 定义数据接口
public interface IBill {
// 这里需要访问者作为参数,用来运行时绑定
// 第二次动态分派,运行时确定visitor的类型
void accept(IVisitor visitor);
}
- 实现具体的数据类型
public class RevenueBill implements IBill {
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class SpendBill implements IBill {
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
- 定义访问者接口,需要确定的知道有哪些数据元素,且需要使用重载的方式定义接口方法。
/**
* 需要知道需要访问的所有元素
*/
public interface IVisitor {
void visit(RevenueBill bill);
void visit(SpendBill bill);
}
- 实现具体的访问者
public class BossView implements IVisitor {
@Override
public void visit(RevenueBill bill) {
System.out.println("Boss view revenue bill");
}
@Override
public void visit(SpendBill bill) {
System.out.println("Boss view spending bill");
}
}
public class FinanceView implements IVisitor {
@Override
public void visit(RevenueBill bill) {
System.out.println("Finance view revenue bill");
}
@Override
public void visit(SpendBill bill) {
System.out.println("Finance view spending bill");
}
}
- 定义稳定数据结构类型,提供方法供客户端程序使用
public class AccountBook {
private List<IBill> billList = new ArrayList<>(2);
public AccountBook() {
billList.add(new RevenueBill());
billList.add(new SpendBill());
}
public void accept(IVisitor visitor) {
for (IBill bill : billList) {
// 第一次动态分派,运行时确定bill的类型
bill.accept(visitor);
}
}
}
- 客户端调用程序
public class Client {
public static void main(String[] args) {
AccountBook accountBook = new AccountBook();
IVisitor visitor = new BossView();
accountBook.accept(visitor);
}
}