行为型 访问者

2020/05/30

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

主要解决:

稳定的数据结构和易变的操作耦合问题

什么时候使用:

需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。

访问者模式的简单例子

我们都知道财务都是有账本的,这个账本就可以作为一个对象结构,而它其中的元素有两种,收入和支出,这满足我们访问者模式的要求,即元素的个数是稳定的,因为账本中的元素只能是收入和支出。

而查看账本的人可能有这样几种,比如老板,会计事务所的注会,财务主管,等等。而这些人在看账本的时候显然目的和行为是不同的。

首先我们给出单子的接口相当于Element,它只有一个方法accept。

public interface Bill {
   void accept(AccountBookViewer v);
}

其中的方法参数AccountBookViewer是一个账本访问者接口,接下来也就是实现类,收入单子和消费单子,或者说收入和支出类。

消费单子

public class ConsumeBill implements Bill {
	
	private double amount;

    private String item;

    public ConsumeBill(double amount, String item) {
        super();
        this.amount = amount;
        this.item = item;
    }
    

	@Override
	public void accept(AccountBookViewer v) {
      v.view(this);
	}
	
	public void OperationA(){
		System.out.println("AAAAAAAAAAAAAAA");
	}
    
	/**
	 * @return the amount
	 */
	public double getAmount() {
		return amount;
	}



	/**
	 * @param amount the amount to set
	 */
	public void setAmount(double amount) {
		this.amount = amount;
	}



	/**
	 * @return the item
	 */
	public String getItem() {
		return item;
	}



	/**
	 * @param item the item to set
	 */
	public void setItem(String item) {
		this.item = item;
	}

}

收入单子

public class IncomeBill implements Bill {

	private double amount;

    private String item;

    public IncomeBill(double amount, String item) {
        super();
        this.amount = amount;
        this.item = item;
    }
	@Override
	public void accept(AccountBookViewer v) {
      v.view(this);
	}
	/**
	 * @return the amount
	 */
	public double getAmount() {
		return amount;
	}
	/**
	 * @param amount the amount to set
	 */
	public void setAmount(double amount) {
		this.amount = amount;
	}
	/**
	 * @return the item
	 */
	public String getItem() {
		return item;
	}
	/**
	 * @param item the item to set
	 */
	public void setItem(String item) {
		this.item = item;
	}
	

}

接下来是账本访问者类

public abstract class AccountBookViewer {
	
	//查看消费的单子
  abstract  void view(ConsumeBill bill);
    	

    //查看收入的单子
  abstract void view(IncomeBill bill);

}

访问者的实现

老板类,老板只关注一共花了多少钱以及一共收入多少钱,其余并不关心

public class Boss extends AccountBookViewer {

	private double totalIncome;

    private double totalConsume;
	
	@Override
	void view(ConsumeBill bill) {
		totalConsume += bill.getAmount();
	}

	@Override
	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;
    }

          
}

注册会计师类,注会在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没,如果是收入,则所有的收入都要交税

public class Cpa extends AccountBookViewer {

	//注会在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没
    public void view(ConsumeBill bill) {
        if (bill.getItem().equals("工资")) {
            System.out.println("注会查看工资是否交个人所得税。");
        }
    }
    //如果是收入,则所有的收入都要交税
    public void view(IncomeBill bill) {
        System.out.println("注会查看收入交税了没。");
    }
    
}

账本类,它是当前访问者模式例子中的对象结构

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);
        }
    }
}

账本类当中有一个列表,这个列表是元素(Bill)的集合,这便是对象结构的通常表示,它一般会是一堆元素的集合,不过这个集合不一定是列表,也可能是树,链表等等任何数据结构,甚至是若干个数据结构。其中show方法,就是账本类的精髓,它会枚举每一个元素,让访问者访问。

测试类

public class Client {

    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();
    }
}

结果

img

上面的代码中,可以这么理解,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。

访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。

如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。

bill.accept(viewer);这段代码真正的执行类是通过运行时两次确定的

分析accept方法的调用过程 1.当调用accept方法时,根据bill的实际类型决定是调用ConsumeBill还是IncomeBill的accept方法。

2.这时accept方法的版本已经确定,假如是ConsumeBill,它的accept方法是调用下面这行代码。

public void accept(AccountBookViewer viewer) {
        viewer.view(this);
    }

此时的this是ConsumeBill类型,所以对应于AccountBookViewer接口的view(ConsumeBill bill)方法,此时需要再根据viewer的实际类型确定view方法的版本,如此一来,就完成了动态双分派的过程。

动态分派与静态分派的区别就是,动态是在运行时才能确定,相当于多台,静态是在编译时候就能确定

设计模式—-访问者模式

Post Directory