`

《设计模式》之十八:访问者模式

阅读更多

Visitor Pattern 访问者模式是的定义如下:

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

封装一些作用于某种数据接口中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

 

访问者模式中的几个角色:

1,Visitor -- 抽象访问接口

声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以访问的。

2,ConcreteVisitor -- 具体访问者

3,Element -- 抽象元素

接口或者抽象类,声明接受哪一类访问者访问,程序上通过accept方法参数去定义

4,ConcreteElement -- 具体元素

实现accept方法,通常是vistor.visit(this),基本上就形成了一种模式了。

5,ObjectStructure -- 结构对象

远程产生者,一般容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。

 

通用源码:

public abstract class Element {
    // 定义业务逻辑
    public abstract void doSomething();
    // 允许谁来访问
    public abstract void accept(IVisitor visitor);
}

 

public class ConcreteElement1 extends Element {
    @Override
    public void doSomething() {
        System.out.println("do1...");
    }

    @Override
    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

 

public class ConcreteElement2 extends Element {
    @Override
    public void doSomething() {
        System.out.println("do2...");
    }

    @Override
    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

 

public interface IVisitor {
    // 可以访问哪些对象
    public void visit(ConcreteElement1 element1);
    public void visit(ConcreteElement2 element2);
}

 

public class Visitor implements IVisitor {
    @Override
    public void visit(ConcreteElement1 element1) {
        element1.doSomething();
    }

    @Override
    public void visit(ConcreteElement2 element2) {
        element2.doSomething();
    }
}

 

public class Client {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                new ConcreteElement1().accept(new Visitor());
            } else {
                new ConcreteElement2().accept(new Visitor());
            }
        }
    }
}

 

访问者模式的优点:

1,符合单一职责原则

具体元素角色Element抽象类的两个子类负责数据的加载,而Visitor类则负责报表的展现,两个具体不同的职责非常明确的分开了,各自演绎变化

2,优先的扩展性

由于职责分开,继续增加数据的操作非常快捷,比如要增加一份给大boss的报表,这份报表格式又有所不同,我们只需要在Visitor中增加一个方法,传递数据后整理打印出来即可,原来的Element不动。

3,灵活性非常高

比如那个Employee的例子,如果要统计所有人的奖金,计算规则是员工工资*0.4,部门经理工资*0.6,总经理的工资*0.8,之前我们怎么做的?那么对所有Employee进行循环,然后用instanceof来判断,我勒个擦,太不优美了,这时候用访问者模式就可以完美解决了,把数据扔给访问者,由访问者来统计计算。

 

访问者模式的缺点:

1,具体的元素Element对访问者公布细节

2,具体元素变更比较困难

3,违背了依赖倒置原则

访问者访问的是具体元素,而不是抽象元素,这破坏了依赖倒置原则,扩展比较困难。

 

访问者模式的使用场景:

1,一个对象结构包含很多类对象,它们有不同的接口,比如上面的Employee的子类有普通员工和经理等。而你想对这些对象实施一些依赖于其具体实现类的操作,也就是说用迭代器模式已经不能胜任了,因为你还得去用instanceof去判断类型,囧。

2,需要对一个对象结构(一般来讲是集合)中的对象进行很多次不同并且不相关的操作,而你想避免让这些操作污染这些类对象。

3,访问者模式还可以充当拦截器Interceptor角色使用。

 

访问者模式的扩展:

统计功能:

统计报表是我们经常需要实现的功能,基本上都是一堆计算公式,然后出一个报表,很多项目采用了数据库的存储过程实现,不过这是不推荐的做法。除非海量数据,一晚上要处理上亿、几十亿条数据,除了存储过程来处理还没有其他办法。

public interface IVistor {
	// 首先定义我可以访问普通员工
	public void visit(CommonEmployee commonEmp);
	// 其次还定义我可以访问部门经理
	public void visit(Manager manager);
	// 统计所有员工的工资总和
	public int getTotalSalary();
}

public class Visitor implements IVisitor {
	// 部门经理的系数是5
	private final int managerRatio = 5;
	// 普通员工的系数是3
	private final int commonRatio = 3;
	// 普通员工的工资总和
	private int commmonTotal = 0;
	// 经理工资总和
	private int managerTotal = 0;
	private void calManager(int salary) {
		managerTotal += salary*managerRatio;
	}
	private void calCommon(int salary) {
		commmonTotal += salary*commonRatio;
	}
	public int getTotalSalary() {
		return managerTotal + commmonTotal;
	}
	public void visit(CommonEmployee commonEmp) {
		System.out.println("哥访问的是普通的员工");
		calCommon(commonEmp.getSalary());
	}
	public void visit(Manager manager) {
		System.out.println("哥访问的是经理");
		calManager(manager.getSalary());
	}
}

public class Client {
	public static void main(String[] args) {
		IVisitor vistor = new Visitor();
		List<Employee> list = null;
		for (Employee emp : list) {
			emp.accept(visitor);
		}
		System.out.println("工资总和:" + vistor.getTotalSalary());
	}
}

 

多个访问者:

上面的其实应该分成两个访问者的,一个是仅仅用来展示数据的report的visitor,而另一个是负责汇总的total的visitor。可以增加两个接口,都继承自IVisitor,但是各自有自己的方法。最后循环中调用

emp.accept(reportVisitor);

emp.accept(totalVisitor); 让所有信息在Visitor实例中积累起来。

然后最后面的时候,就可以直接调用各自的方法了。

 

双分派问题:

public interface Role {
    public void accept(AbsActor actor);
}

 

public class UglyRole implements Role {
    @Override
    public void accept(AbsActor actor) {
        actor.act(this);
    }
}

 

public class BeautyRole implements Role {
    @Override
    public void accept(AbsActor actor) {
        actor.act(this);
    }
}

 

public class Client {
    public static void main(String[] args) {
        // 定义一个演员
        AbsActor actor = new UglyActor();
        // 定义一个角色
        Role role = new BeautyRole();
//        // 开始演戏
//        actor.act(role);
//        // 动态绑定
//        actor.act(new BeautyRole());
        // 开始演戏(访问者模式)
        role.accept(actor);
    }
}

 

本人博客已搬家,新地址为:http://yidao620c.github.io/

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics