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/