引言
23设计模式这是最后一篇了,到此就结尾了,先回顾一下上一篇所讲的解释器模式,然后看看今天的访问者模式。
示例地址
Demo
类图
定义
封装一些作用于某些数据结构的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
使用场景
1. 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
访问者模式中的角色
1. Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
2. ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
3. Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
4. ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
5. ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
访问者模式示例
1. Visitor抽象类
/**
* @author [email protected]
* @created 2018/8/20 上午10:35.
*/
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
2. Visitor实现类ConcreteVisitorA
/**
* @author [email protected]
* @created 2018/8/20 上午10:36.
*/
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
}
@Override
public void visit(ConcreteElementB elementB) {
}
}
3. Visitor实现类ConcreteVisitorB
/**
* @author [email protected]
* @created 2018/8/20 上午10:36.
*/
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
}
@Override
public void visit(ConcreteElementB elementB) {
}
}
4. Element接口
/**
* @author [email protected]
* @created 2018/8/20 上午10:37.
*/
public interface Element {
void accept(Visitor visitor);
}
5. Element实现类ConcreteElementA
/**
* @author [email protected]
* @created 2018/8/20 上午10:37.
*/
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
System.out.println(visitor.getClass().getCanonicalName() + "=====访问" +"ConcreteElementA");
visitor.visit(this);
}
}
6. Element实现类ConcreteElementB
/**
* @author [email protected]
* @created 2018/8/20 上午10:37.
*/
public class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
System.out.println(visitor.getClass().getCanonicalName() + "=======访问"+ "ConcreteElementB");
visitor.visit(this);
}
}
7. ObjectStructure
/**
* @author [email protected]
* @created 2018/8/20 上午10:39.
*/
public class ObjectStructure {
private List mElements = new ArrayList<>();
public void addElement(Element e) {
mElements.add(e);
}
public void removeElement(Element e) {
mElements.remove(e);
}
public void accpet(Visitor visitor) {
for (Element e : mElements) {
e.accept(visitor);
}
}
}
8. Client
ObjectStructure os = new ObjectStructure();
os.addElement(new ConcreteElementA());
os.addElement(new ConcreteElementB());
//创建一个访问者
Visitor visitor = new ConcreteVisitorA();
os.accpet(visitor);
访问者模式中的伪动态双分派
-
静态分派
静态分派就是按照变量的静态类型进行分派,从而确定方法的执行版本,静态分派在编译时期就可以确定方法的版本。静态分派最典型的应用就是方法重载。
public class StaticDispatch {
public void test(String string){
System.out.println("this is string");
}
public void test(Integer integer){
System.out.println("this is integer");
}
public static void staticDispatch(String[] args) {
String s1 ="aaaaa";
Integer a = 1;
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.test(a);
staticDispatch.test(s1);
}
}
-
动态分派
与静态相反,它不是在编译期确定的方法版本,而是在运行时才能确定。而动态分派最典型的应用就是多态的特性。
public interface Animal{
void test();
}
public class Dog implements Animal{
public void test(){
System.out.println("dog");
}
}
public class Cat implements Animal{
public void test(){
System.out.println("cat");
}
}
public class DynamicDispatch {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.test();
cat.test();
}
}
- 伪动态双分派
访问者模式中使用的是伪动态双分派,所谓的动态双分派就是在运行时依据两个实际类型去判断一个方法的运行行为,而访问者模式实现的手段是进行了两次动态单分派来达到这个效果。
public void accpet(Visitor visitor) {
for (Element e : mElements) {
e.accept(visitor);
}
}
访问者模式实际运用
1. Visitor
/**
* Visitor
*
* @author [email protected]
* @created 2018/8/20 下午12:46.
*/
public interface AccountBookViewer {
//查看消费的单子
void view(ConsumeBill bill);
//查看收入的单子
void view(IncomeBill bill);
}
2. 老板
/**
* visitor 老板
*
* @author [email protected]
* @created 2018/8/20 下午12:47.
*
*/
public 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;
}
}
3. 会计
/**
* visitor 会计
*
* @author [email protected]
* @created 2018/8/20 下午12:47.
*/
public class CPA implements AccountBookViewer {
//注会在看账本时,如果是支出,则如果支出是工资,则需要看应该交的税交了没
public void view(ConsumeBill bill) {
if (bill.getItem().equals("工资")) {
System.out.println("注会查看工资是否交个人所得税。");
}
}
//如果是收入,则所有的收入都要交税
public void view(IncomeBill bill) {
System.out.println("注会查看收入交税了没。");
}
}
4. Element
/**
* Element
*
* @author [email protected]
* @created 2018/8/20 下午12:49.
*/
public interface Bill {
void accept(AccountBookViewer viewer);
}
5. 消费
/**
* 消费
*
* @author [email protected]
* @created 2018/8/20 下午12:50.
*/
public class ConsumeBill implements Bill {
private double amount;
private String item;
public ConsumeBill(double amount, String item) {
super();
this.amount = amount;
this.item = item;
}
public void accept(AccountBookViewer viewer) {
viewer.view(this);
}
public double getAmount() {
return amount;
}
public String getItem() {
return item;
}
}
6. 收入的单子
/**
* 收入的单子
*
* @author [email protected]
* @created 2018/8/20 下午12:51.
*/
public class IncomeBill implements Bill{
private double amount;
private String item;
public IncomeBill(double amount, String item) {
super();
this.amount = amount;
this.item = item;
}
public void accept(AccountBookViewer viewer) {
viewer.view(this);
}
public double getAmount() {
return amount;
}
public String getItem() {
return item;
}
}
7. 账簿
/**
* ObjectStruture
*
* @author [email protected]
* @created 2018/8/20 下午12:52.
*/
public class AccountBook {
//单子列表
private List billList = new ArrayList();
//添加单子
public void addBill(Bill bill) {
billList.add(bill);
}
//供账本的查看者查看账本
public void show(AccountBookViewer viewer) {
for (Bill bill : billList) {
bill.accept(viewer);
}
}
}
8. Client
AccountBook accountBook = new AccountBook();
//添加收入
accountBook.addBill(new IncomeBill(10000, "卖商品"));
//添加支出
accountBook.addBill(new ConsumeBill(1000, "工资"));
AccountBookViewer boss = new Boss();
AccountBookViewer cpa = new CPA();
//两个访问者分别访问账本
accountBook.show(cpa);
accountBook.show(boss);
((Boss) boss).getTotalConsume();
((Boss) boss).getTotalIncome();
总结
-
优点
- 使得对象结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
添加新的操作或者说访问者会非常容易,无须修改源代码,符合“开闭原则”。 - 将有关元素对象的操作集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
- 使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
- 可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。
-
缺点
- 增加新的元素会非常困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
- 实现起来比较复杂,会增加系统的复杂性。
- 破坏封装,如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态,但使用访问者模式的时候,为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构。