Visitor Pattern 访问者模式是的定义如下:
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.
封装一些作用于某种数据接口中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
访问者模式中的几个角色:
1,Visitor -- 抽象访问接口
声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以访问的。
2,ConcreteVisitor -- 具体访问者
3,Element -- 抽象元素
接口或者抽象类,声明接受哪一类访问者访问,程序上通过accept方法参数去定义
4,ConcreteElement -- 具体元素
实现accept方法,通常是vistor.visit(this),基本上就形成了一种模式了。
5,ObjectStructure -- 结构对象
远程产生者,一般容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
通用源码:
public abstract class Element {
// 定义业务逻辑
public abstract void doSomething();
// 允许谁来访问
public abstract void accept(IVisitor visitor);
}
public class ConcreteElement1 extends Element {
@Override
public void doSomething() {
System.out.println("do1...");
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element {
@Override
public void doSomething() {
System.out.println("do2...");
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public interface IVisitor {
// 可以访问哪些对象
public void visit(ConcreteElement1 element1);
public void visit(ConcreteElement2 element2);
}
public class Visitor implements IVisitor {
@Override
public void visit(ConcreteElement1 element1) {
element1.doSomething();
}
@Override
public void visit(ConcreteElement2 element2) {
element2.doSomething();
}
}
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
new ConcreteElement1().accept(new Visitor());
} else {
new ConcreteElement2().accept(new Visitor());
}
}
}
}
访问者模式的优点:
1,符合单一职责原则
具体元素角色Element抽象类的两个子类负责数据的加载,而Visitor类则负责报表的展现,两个具体不同的职责非常明确的分开了,各自演绎变化
2,优先的扩展性
由于职责分开,继续增加数据的操作非常快捷,比如要增加一份给大boss的报表,这份报表格式又有所不同,我们只需要在Visitor中增加一个方法,传递数据后整理打印出来即可,原来的Element不动。
3,灵活性非常高
比如那个Employee的例子,如果要统计所有人的奖金,计算规则是员工工资*0.4,部门经理工资*0.6,总经理的工资*0.8,之前我们怎么做的?那么对所有Employee进行循环,然后用instanceof来判断,我勒个擦,太不优美了,这时候用访问者模式就可以完美解决了,把数据扔给访问者,由访问者来统计计算。
访问者模式的缺点:
1,具体的元素Element对访问者公布细节
2,具体元素变更比较困难
3,违背了依赖倒置原则
访问者访问的是具体元素,而不是抽象元素,这破坏了依赖倒置原则,扩展比较困难。
访问者模式的使用场景:
1,一个对象结构包含很多类对象,它们有不同的接口,比如上面的Employee的子类有普通员工和经理等。而你想对这些对象实施一些依赖于其具体实现类的操作,也就是说用迭代器模式已经不能胜任了,因为你还得去用instanceof去判断类型,囧。
2,需要对一个对象结构(一般来讲是集合)中的对象进行很多次不同并且不相关的操作,而你想避免让这些操作污染这些类对象。
3,访问者模式还可以充当拦截器Interceptor角色使用。
访问者模式的扩展:
统计功能:
统计报表是我们经常需要实现的功能,基本上都是一堆计算公式,然后出一个报表,很多项目采用了数据库的存储过程实现,不过这是不推荐的做法。除非海量数据,一晚上要处理上亿、几十亿条数据,除了存储过程来处理还没有其他办法。
public interface IVistor {
// 首先定义我可以访问普通员工
public void visit(CommonEmployee commonEmp);
// 其次还定义我可以访问部门经理
public void visit(Manager manager);
// 统计所有员工的工资总和
public int getTotalSalary();
}
public class Visitor implements IVisitor {
// 部门经理的系数是5
private final int managerRatio = 5;
// 普通员工的系数是3
private final int commonRatio = 3;
// 普通员工的工资总和
private int commmonTotal = 0;
// 经理工资总和
private int managerTotal = 0;
private void calManager(int salary) {
managerTotal += salary*managerRatio;
}
private void calCommon(int salary) {
commmonTotal += salary*commonRatio;
}
public int getTotalSalary() {
return managerTotal + commmonTotal;
}
public void visit(CommonEmployee commonEmp) {
System.out.println("哥访问的是普通的员工");
calCommon(commonEmp.getSalary());
}
public void visit(Manager manager) {
System.out.println("哥访问的是经理");
calManager(manager.getSalary());
}
}
public class Client {
public static void main(String[] args) {
IVisitor vistor = new Visitor();
List<Employee> list = null;
for (Employee emp : list) {
emp.accept(visitor);
}
System.out.println("工资总和:" + vistor.getTotalSalary());
}
}
多个访问者:
上面的其实应该分成两个访问者的,一个是仅仅用来展示数据的report的visitor,而另一个是负责汇总的total的visitor。可以增加两个接口,都继承自IVisitor,但是各自有自己的方法。最后循环中调用
emp.accept(reportVisitor);
emp.accept(totalVisitor); 让所有信息在Visitor实例中积累起来。
然后最后面的时候,就可以直接调用各自的方法了。
双分派问题:
public interface Role {
public void accept(AbsActor actor);
}
public class UglyRole implements Role {
@Override
public void accept(AbsActor actor) {
actor.act(this);
}
}
public class BeautyRole implements Role {
@Override
public void accept(AbsActor actor) {
actor.act(this);
}
}
public class Client {
public static void main(String[] args) {
// 定义一个演员
AbsActor actor = new UglyActor();
// 定义一个角色
Role role = new BeautyRole();
// // 开始演戏
// actor.act(role);
// // 动态绑定
// actor.act(new BeautyRole());
// 开始演戏(访问者模式)
role.accept(actor);
}
}
本人博客已搬家,新地址为:http://yidao620c.github.io/