访问者模式
一、定义
访问者模式(Visitor Pattern)的定义如下:
Represent an operation to be performed on the element of an object structure. Visitor lets you define a new operation without changing the classes of elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作)
访问者模式的通用类图如上所示。由图可知,状态模式中有5个角色:
- Visitor - 抽象访问者:接口或抽象类,声明访问者可以访问哪些元素。
- ConcreteVisitor - 具体访问者:它影响到访问者访问到一个类后该怎么干。
- Element - 抽象元素:接口或抽象类,声明接受哪一类访问者访问。
- ConcreteElement - 具体元素:实现accept方法,通常是visitor.visit(this)
- ObjectStructure - 结构对象:元素生产者,一般容纳在多个不同类、不同接口的容器中,例如List、Set、Map等。
状态模式的核心:封装。状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变。
二、通用代码
1. 抽象元素
public abstract class Element{
//业务逻辑
public abstract void doSomething();
//允许谁来访问
public abstract void accept(IVisitor visitor);
}
抽象元素有2类方法:一是本身的业务逻辑;二是允许哪一个访问者来访问。
2. 具体元素
public class ConcreteElement1 extends Element {
//完善业务逻辑
public void handle1() {
//业务处理
}
//处理哪个访问者访问
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element {
//完善业务逻辑
public void handle1() {
//业务处理
}
//处理哪个访问者访问
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
这里定义了2个具体元素。
3. 抽象访问者
public interface IVisitor {
//可以访问哪些对象
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}
一般来说,有几个具体元素,抽象访问者就有几个访问方法
。
4. 具体访问者
public class Visitor implements IVisitor {
//可以el1对象
public void visit(ConcreteElement1 el1) {
el1.doSomething();
}
//可以el2对象
public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}
一般来说,有几个具体元素,抽象访问者就有几个访问方法
。
5. 结构对象
结构对象是产生出不同的元素对象,我们使用工厂方法模式来模拟,如下:
public class ObjectStructure {
public static Element createElement() {
Random rand = new Random();
if (rand.nextInt(100) > 50) {
return new ConcreteElement1();
}else {
return new COncreteElement2();
}
}
}
6. 场景类
public class Client {
public static void main(String[] args) {
//定义环境角色
for (int i=0; i<10; i++) {
//获得元素对象
Element e1 = ObjectStructure.createElement();
//接受访问者访问
e1.accept(new Visitor());
}
}
}
三、实际例子
员工定制报表。
1. 抽象元素
public abstract class Employee {
public final static int MALE = 0; //0代表是男性
public final static int FEMALE = 1; //1代表是女性
private String name;
private int salary;
private int sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
//允许一个访问者访问
public abstract void accept(IVisitor visitor);
}
2. 具体元素
/**
* 普通员工
*/
public class CommonEmployee extends Employee{
private String job;
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
/**
* 部门经理
*/
public class Manager extends Employee{
private String performance;
public String getPerformance() {
return performance;
}
public void setPerformance(String performance) {
this.performance = performance;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
3. 访问者接口
public interface IVisitor {
//访问普通员工
public void visit(CommonEmployee commonEmployee);
//访问部门经理
public void visit(Manager manager);
}
4. 访问者实现
public class Visitor implements IVisitor {
@Override
public void visit(CommonEmployee commonEmployee) {
System.out.println(this.getCommonEmployee(commonEmployee));
}
@Override
public void visit(Manager manager) {
System.out.println(this.getManagerInfo(manager));
}
//组装出基本信息
private String getBasicInfo(Employee employee) {
String info = "姓名:" + employee.getName() + "\t";
info = info + "性别" + (employee.getSex() == Employee.FEMALE ? "女" : "男") + "\t";
info = info + "薪水" + employee.getSalary() + "\t";
return info;
}
//组装出部门经理的信息
private String getManagerInfo(Manager manager) {
String basicInfo = this.getBasicInfo(manager);
String otherInfo = "业绩:" + manager.getPerformance() + "\t";
return basicInfo + otherInfo;
}
//组装出普通员工的信息
private String getCommonEmployee(CommonEmployee commonEmployee) {
String basicInfo = this.getBasicInfo(commonEmployee);
String otherInfo = "工作:" + commonEmployee.getJob() + "\t";
return basicInfo + otherInfo;
}
}
5. 场景类
public class Client {
public static void main(String[] args) {
for (Employee emp:mockEmployee()) {
emp.accept(new Visitor());
}
}
public static List<Employee> mockEmployee() {
ArrayList<Employee> empList = new ArrayList<>();
CommonEmployee zhangSan = new CommonEmployee();
zhangSan.setJob("编写Java程序,绝对的蓝领,苦工加搬运工");
zhangSan.setName("张三");
zhangSan.setSex(Employee.MALE);
zhangSan.setSalary(1900);
empList.add(zhangSan);
CommonEmployee liSi = new CommonEmployee();
liSi.setJob("页面美工,审美素质太不流行了");
liSi.setName("李四");
liSi.setSex(Employee.FEMALE);
liSi.setSalary(1800);
empList.add(liSi);
Manager wangWu = new Manager();
wangWu.setName("王五");
wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
wangWu.setSex(Employee.MALE);
wangWu.setSalary(13000);
empList.add(wangWu);
return empList;
}
}
执行结果如下:
姓名:张三 性别男 薪水1900 工作:编写Java程序,绝对的蓝领,苦工加搬运工
姓名:李四 性别女 薪水1800 工作:页面美工,审美素质太不流行了
姓名:王五 性别男 薪水13000 业绩:基本上是负值,但是我会拍马屁呀
当需要改变报表时,仅需新增访问者的实现类。
四、访问者模式的应用
1. 访问模式的优点和缺点
优点:符合单一职责原则、扩展性高和灵活性高。
缺点:具体元素对访问者公开,具体元素变更困难,违背依赖倒置原则。
2. 访问者模式的使用场景
- 一个对象结构包含很多类对象,它们有不同的接口,而需要对这些对象实施一些依赖于其具体类的操作中,也就是迭代器模式不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而想要避免这些操作“污染”这些对象的类。
也就是说需要遍历不同的对象,执行不同操作的情景
。
3. 最佳实践
是一种集中规整的模式,适合大规模重构的项目,在这一阶段,功能点明确,可以很容易将一些功能进行梳理。此外,还可以与其他模式混编建立拦截器。