什么是访问者模式?
一个对象有稳定的数据结构,却为不同的访问者提供不同的数据操作,对象提供接收访问者的方法,从而保证数据结构的稳定性和操作的多样性。也可以理解为,封装对象的操作方法,达到不改变对象数据结构的稳定性同时易于扩展操作。
解决的主要问题
主要解决:稳定的数据结构和易变的操作耦合问题。
如何实现
(1)Visitor接口:访问者接口,封装对象元素的操作,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法数理论上来讲与元素个数是一样的。
(2)Visitor1、Visitor2:访问者 -- 具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为,如老板,会计。
(3)Element:元素对外访问入口接口,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素(子类)都要可以被访问者访问。
(4)ElementA、ElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
(5)Object:定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问
生活场景
公司有一个账本,抽象为一个对象,它有两个稳定的元素,一个是收入,一个是支出。公司有不同的角色都需要访问账本,抽象为访问者。比如。老板只关注总收入和总支出,会计关心每一笔收入是否缴税,每发一笔工资是否扣税。两者间是不同操作,且还有以后可能其他角色需要访问账本。
demo代码
1、Visitor接口
/** * 访问者接口 * (1)查看收入账单 * (1)查看之处账单 */ public interface IAccountBookViewer { /** * 消费账单 * @param bill */ void view(ConsumeBill bill); /** * 收入账单 * @param bill */ void view(IncomeBill bill); }
2、访问者
2.1 具体类 -- 老板
1 /** 2 * 老板角色--查看账单 3 * 老板只关注总收入和总支出 4 */ 5 public class Boss implements IAccountBookViewer { 6 7 /** 8 * 总消费 9 */ 10 private Double totalConsume = 0d;//默认值是null 11 12 /** 13 * 总收入 14 */ 15 private Double totalIncome = 0d;//默认值是null 16 17 public Double getTotalConsume() { 18 System.out.println("老板查看总支出:" + totalConsume); 19 return totalConsume; 20 } 21 22 public Double getTotalIncome() { 23 System.out.println("老板查看总收入:" + totalIncome); 24 return totalIncome; 25 } 26 27 28 /** 29 * 查看消费账单 30 * 31 * @param bill 消费账单 32 */ 33 @Override 34 public void view(ConsumeBill bill) { 35 totalConsume += bill.getAmount(); 36 } 37 38 /** 39 * 查看收入账单 40 * 41 * @param bill 账单 42 */ 43 @Override 44 public void view(IncomeBill bill) { 45 totalIncome += bill.getAmount(); 46 } 47 }
2.2、具体类 -- 会计
1 /** 2 * 注册会计师 3 * 关注具体收入和支出是否交税 4 */ 5 public class CPA implements IAccountBookViewer { 6 /** 7 * 查看支出,如果是工资,是否已经交税 8 * 9 * @param bill 10 */ 11 @Override 12 public void view(ConsumeBill bill) { 13 if (bill.getItem().equals("工资")) { 14 System.out.println("CPA查看是否工资已经扣税"); 15 } 16 } 17 18 /** 19 * 查看收入,所有收入都要交税 20 * 21 * @param bill 22 */ 23 @Override 24 public void view(IncomeBill bill) { 25 System.out.println("CPA查看所有收入是否已经缴税"); 26 } 27 }
3、元素接口,定义每个元素行为 -- 对外提供accept方法,传入访问者
1 /** 2 * 账单类接口,接收访问者 3 */ 4 public interface IBill { 5 void accept(IAccountBookViewer viewer); 6 }
4、具体的元素类
4.1、收入条目,可以理解为一个对象的元素
1 /** 2 * 收入账单 3 */ 4 public class IncomeBill implements IBill { 5 /** 6 * 收入明细金额 7 */ 8 private Double amount; 9 10 /** 11 * 收入条目 12 */ 13 private String item; 14 15 /** 16 * 收入明细金额 17 */ 18 public Double getAmount() { 19 return amount; 20 } 21 22 /** 23 * 收入条目 24 */ 25 public String getItem() { 26 return item; 27 } 28 29 30 public IncomeBill(Double amount, String item) { 31 this.amount = amount; 32 this.item = item; 33 } 34 35 @Override 36 public void accept(IAccountBookViewer viewer) { 37 viewer.view(this); 38 } 39 }
4.2、支出条目,可以理解为一个对象元素
1 /** 2 * 消费账单 3 */ 4 public class ConsumeBill implements IBill { 5 6 /** 7 * 支出明细金额 8 */ 9 private Double amount; 10 11 /** 12 * 支出条目 13 */ 14 private String item; 15 16 /** 17 * 支出明细金额 18 */ 19 public Double getAmount() { 20 return amount; 21 } 22 23 /** 24 * 支出条目 25 */ 26 public String getItem() { 27 return item; 28 } 29 30 31 public ConsumeBill(Double amount, String item) { 32 this.amount = amount; 33 this.item = item; 34 } 35 36 @Override 37 public void accept(IAccountBookViewer viewer) { 38 viewer.view(this); 39 } 40 }
5、对象类
包含具体元素的集合,提供访问集合元素的轮询方法show
1 import java.util.ArrayList; 2 import java.util.List; 3 4 /** 5 * 账本类 6 */ 7 public class AccountBook { 8 /** 9 * 账单条目列表 10 */ 11 private List<IBill> billList = new ArrayList<IBill>(); 12 13 /** 14 * 增加账单条目 15 * @param bill 账单条目 16 */ 17 public void addBill(IBill bill){ 18 billList.add(bill); 19 } 20 21 /** 22 * 访问者产看账本 23 * @param viewer 24 */ 25 public void show(IAccountBookViewer viewer){ 26 for (IBill bill : billList) { 27 bill.accept(viewer); 28 } 29 } 30 31 }
测试入口
1 public class App { 2 public static void (String[] args) { 3 //创建账本 4 AccountBook accBook = new AccountBook(); 5 6 //收入条目 7 accBook.addBill(new IncomeBill(10000d, "广告收入")); 8 accBook.addBill(new IncomeBill(900000d, "房地产项目")); 9 10 //支出条目 11 accBook.addBill(new ConsumeBill(20000d, "工资")); 12 accBook.addBill(new ConsumeBill(10000d, "办公室租金")); 13 accBook.addBill(new ConsumeBill(8000d, "水电费")); 14 15 //访问者 16 Boss boss = new Boss(); 17 CPA cpa = new CPA(); 18 19 //访问者查看账单 20 accBook.show(boss); 21 accBook.show(cpa); 22 23 boss.getTotalConsume(); 24 boss.getTotalIncome(); 25 } 26 } 27
输出结果
1 CPA查看所有收入是否已经缴税 2 CPA查看所有收入是否已经缴税 3 CPA查看是否工资已经扣税 4 老板查看总支出:38000.0 5 老板查看总收入:910000.0