定义与特点
- 定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
详细概述
- 访问者模式属于行为型模式。
- 访问者模式是一种将数据结构和数据操作分离的设计模式。
- 访问者模式比较复杂,而且实际使用的地方并不多。
- 访问者模式适用于数据结构稳定的元素操作上,一旦数据结构易变,则不适用。
参与角色
- Visitor(抽象访问者):接口或者抽象类,为每一个元素(Element)声明一个访问的方法。
- ConcreteVisitor(具体访问者):实现抽象访问者中的方法,即对每一个元素都有其具体的访问行为。
- Element(抽象元素):接口或者抽象类,定义一个accept方法,能够接受访问者(Visitor)的访问。
- ConcreteElement(具体元素):实现抽象元素中的accept方法,通常是调用访问者提供的访问该元素的方法。
- Client(客户端类):即要使用访问者模式的地方。
- 类结构图
结构代码示例
定义抽象访问者
interface Visitor { void visit(ConcreteElementA element); void visit(ConcreteElementB element); }
定义具体访问者
//具体访问者A类 class ConcreteVisitorA implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者A访问-->" + element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者A访问-->" + element.operationB()); } } //具体访问者B类 class ConcreteVisitorB implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者B访问-->" + element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者B访问-->" + element.operationB()); } }
定义抽象元素
interface Element { void accept(Visitor visitor); }
定义具体元素
//具体元素A类 class ConcreteElementA implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationA() { return "具体元素A的操作。"; } } //具体元素B类 class ConcreteElementB implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationB() { return "具体元素B的操作。"; } }
定义对象结构角色
class ObjectStructure { private List<Element> list = new ArrayList<Element>(); public void accept(Visitor visitor) { Iterator<Element> i = list.iterator(); while (i.hasNext()) { ((Element) i.next()).accept(visitor); } } public void add(Element element) { list.add(element); } public void remove(Element element) { list.remove(element); } }
客户端调用测试
public class Client { public static void main(String[] args) { ObjectStructure os = new ObjectStructure(); os.add(new ConcreteElementA()); os.add(new ConcreteElementB()); Visitor visitor = new ConcreteVisitorA(); os.accept(visitor); System.out.println("------------------------"); visitor = new ConcreteVisitorB(); os.accept(visitor); } } //输出结果 具体访问者A访问-->具体元素A的操作。 具体访问者A访问-->具体元素B的操作。 ------------------------ 具体访问者B访问-->具体元素A的操作。 具体访问者B访问-->具体元素B的操作。
案例分析
我们都知道财务都是有账本的,这个账本就可以作为一个对象结构,而它其中的元素有两种,收入和支出,这满足我们访问者模式的要求,即元素的个数是稳定的,因为账本中的元素只能是收入和支出。
而查看账本的人可能有这样几种,比如老板,会计事务所的注会,财务主管,等等。而这些人在看账本的时候显然目的和行为是不同的。
定义账单查看者(抽象访问者)
interface AccountBookViewer { //查看消费的单子 void view(ConsumeBill bill); //查看收入的单子 void view(IncomeBill bill); }
定义会计、老板(具体访问者)
//注册会计师类,查看账本的类之一 class CPA implements AccountBookViewer { // 注会在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没 public void view(ConsumeBill bill) { if (bill.getItem().equals("工资")) { System.out.println("注会查看工资是否交个人所得税。"); } } // 如果是收入,则所有的收入都要交税 public void view(IncomeBill bill) { System.out.println("注会查看收入交税了没。"); } } //老板类,查看账本的类之一 class Boss implements AccountBookViewer { private double totalIncome; private double totalConsume; // 老板只关注一共花了多少钱以及一共收入多少钱,其余并不关心 public void view(ConsumeBill bill) { totalConsume += bill.getAmount(); } public void view(IncomeBill bill) { totalIncome += bill.getAmount(); } public double getTotalIncome() { System.out.println("老板查看一共收入多少,数目是:" + totalIncome); return totalIncome; } public double getTotalConsume() { System.out.println("老板查看一共花费多少,数目是:" + totalConsume); return totalConsume; } }
定义单子(抽象元素)
interface Bill { void accept(AccountBookViewer viewer); }
定义消费单子、收入单子(具体元素)
//消费的单子 class ConsumeBill implements Bill { private double amount; private String item; public ConsumeBill(double amount, String item) { this.amount = amount; this.item = item; } public void accept(AccountBookViewer viewer) { viewer.view(this); } public double getAmount() { return amount; } public String getItem() { return item; } } //收入单子 class IncomeBill implements Bill { private double amount; private String item; public IncomeBill(double amount, String item) { this.amount = amount; this.item = item; } public void accept(AccountBookViewer viewer) { viewer.view(this); } public double getAmount() { return amount; } public String getItem() { return item; } }
定义账本类(对象结构角色)
class AccountBook { // 单子列表 private List<Bill> billList = new ArrayList<Bill>(); // 添加单子 public void addBill(Bill bill) { billList.add(bill); } // 供账本的查看者查看账本 public void show(AccountBookViewer viewer) { for (Bill bill : billList) { bill.accept(viewer); } } }
客户端调用程序
public class VisitorTest { public static void main(String[] args) { AccountBook accountBook = new AccountBook(); //添加两条收入 accountBook.addBill(new IncomeBill(10000, "卖商品")); accountBook.addBill(new IncomeBill(12000, "卖广告位")); //添加两条支出 accountBook.addBill(new ConsumeBill(1000, "工资")); accountBook.addBill(new ConsumeBill(2000, "材料费")); AccountBookViewer boss = new Boss(); AccountBookViewer cpa = new CPA(); //两个访问者分别访问账本 accountBook.show(cpa); accountBook.show(boss); ((Boss) boss).getTotalConsume(); ((Boss) boss).getTotalIncome(); } }
上面的代码中,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。
访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。
如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。
总结
优点
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
- 添加新的操作或者说访问者会非常容易。
- 将对各个元素的一组操作集中在一个访问者类当中。
- 使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
- 可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。
缺点
- 增加新的元素会非常困难。
- 实现起来比较复杂,会增加系统的复杂性。
- 破坏封装,如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态,但使用访问者模式的时候,为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构,就像收入和支出类必须提供访问金额和单子的项目的方法一样。
适用场景
- 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。