封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
老板和会计查看账本的收入与支出
抽象元素
package demo29; /** * * @ClassName: Bill * @Description:抽象账单 * @author cheng * @date 2017-8-30 下午04:57:33 */ public interface Bill { /** * * @Title: accept * @Description:接收的访问者 * @param viewer */ void accept(AccountBookViewer viewer); }具体元素
package demo29; /** * * @ClassName: ConsumeBill * @Description:消费账单 * @author cheng * @date 2017-8-30 下午05:01:25 */ public class ConsumeBill implements Bill { private double amount;// 消费金额 private String item;// 消费类别 /** * 构造函数 * * @param amount * @param item */ public ConsumeBill(double amount, String item) { super(); this.amount = amount; this.item = item; } /** * 复写 */ public void accept(AccountBookViewer viewer) { viewer.viewConsumeBill(this); } /** * * @Title: getAmount * @Description: 获取消费账单金额 * @return */ public double getAmount() { return amount; } public String getItem() { return item; } } package demo29; /** * * @ClassName: IncomeBill * @Description:收入账单 * @author cheng * @date 2017-8-30 下午05:03:56 */ public class IncomeBill implements Bill { private double amount;// 收入金额 private String item;// 收入类别 /** * 构造函数 * * @param amount * @param item */ public IncomeBill(double amount, String item) { super(); this.amount = amount; this.item = item; } /** * 复写 */ public void accept(AccountBookViewer viewer) { viewer.viewIncomeBill(this); } /** * * @Title: getAmount * @Description: 获取收入账单金额 * @return */ public double getAmount() { return amount; } public String getItem() { return item; } }抽象访问者
package demo29; /** * * @ClassName: AccountBookViewer * @Description:抽象访问者 * @author cheng * @date 2017-8-30 下午04:59:29 */ public interface AccountBookViewer { /** * * @Title: viewConsumeBill * @Description: 查看消费的账单 * @param bill */ void viewConsumeBill(ConsumeBill bill); /** * * @Title: viewIncomeBill * @Description: 查看收入的账单 * @param bill */ void viewIncomeBill(IncomeBill bill); }具体访问者
package demo29; /** * * @ClassName: Boss * @Description: 老板类,查看账本的类之一 * @author cheng * @date 2017-8-30 下午05:05:37 */ public class Boss implements AccountBookViewer { private double totalIncome;// 总收入 private double totalConsume;// 总消费 @Override public void viewConsumeBill(ConsumeBill bill) { totalConsume += bill.getAmount(); } @Override public void viewIncomeBill(IncomeBill bill) { totalIncome += bill.getAmount(); } public double getTotalIncome() { System.out.println("老板查看一共收入多少,数目是:" + totalIncome); return totalIncome; } public double getTotalConsume() { System.out.println("老板查看一共花费多少,数目是:" + totalConsume); return totalConsume; } } package demo29; /** * * @ClassName: CPA * @Description: 注册会计师类,查看账本的类之一 * @author chengrui * @date 2017-8-30 下午05:07:09 */ public class CPA implements AccountBookViewer { /** * 注册会计师在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没 */ @Override public void viewConsumeBill(ConsumeBill bill) { if (bill.getItem().equals("工资")) { System.out.println("注册会计师查看工资是否交个人所得税。"); } } /** * 如果是收入,则所有的收入都要交税 */ @Override public void viewIncomeBill(IncomeBill bill) { System.out.println("注册会计师查看收入交税了没。"); } }结构对象
package demo29; import java.util.ArrayList; import java.util.List; /** * * @ClassName: AccountBook * @Description:账本 * @author cheng * @date 2017-8-30 下午05:09:25 */ public 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); } } }测试
package demo29; /** * * @ClassName: ClientTest * @Description:测试 * @author cheng * @date 2017-8-30 下午05:10:26 */ public class ClientTest { 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(); } }运行结果
但是,访问者模式并不是那么完美,它也有着致命的缺陷:
增加新的元素类比较困难。 通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。
一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来 定义在一个类中。
当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
正如《设计模式》的作者GoF对访问者模式的描述:大多数情况下,你并需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要它了。当然这只是针对真正的大牛而言。在现实情况下(至少是我所处的环境当中),很多人往往沉迷于设计模式,他们使用一种设计模式时,从来不去认真考虑所使用的模式是否适合这种场景,而往往只是想展示一下自己对面向对象设计的驾驭能力。编程时有这种心理,往往会发生滥用设计模式的情况。所以,在学习设计模式时,一定要理解模式的适用性。必须做到使用一种模式是因为了解它的优点,不使用一种模式是因为了解它的弊端;而不是使用一种模式是因为不了解它的弊端,不使用一种模式是因为不了解它的优点。