一、场景问题
某公司人事部门和财务部门都会处理员工的基础数据。
- 财务部门:会访问员工的薪水情况
- 人事部门:会访问员工的出勤情况
员工分为正式员工和临时工
二、传统解决方案
定义抽象部门接口,财务部门和人事部门为其实现子类,分别在各自的类中处理逻辑,这种方式也能很方便解决问题,至于访问者模式,会导致代码抽象,晦涩难懂,不建议使用访问者模式。下文仅仅作为模式的案例展示。
三、模式剖析
1、模式定义
访问者模式(Visitor Pattern):将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
2、模式结构
访问者模式一般包含如下角色
-
Visitor
:抽象访问角色定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
//抽象访问者 interface Visitor { void visit(ConcreteElementA element); void visit(ConcreteElementB element); }
-
Concrete Visitor
:具体访问者角色实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
//具体访问者A类 class ConcreteVisitorA implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者A访问-->"+element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者A访问-->"+element.operationB()); } } //具体访问者B类 class ConcreteVisitorB implements Visitor { public void visit(ConcreteElementA element) { System.out.println("具体访问者B访问-->"+element.operationA()); } public void visit(ConcreteElementB element) { System.out.println("具体访问者B访问-->"+element.operationB()); } }
-
Element
:抽象元素角色声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数
//抽象元素类 interface Element { void accept(Visitor visitor); }
-
Concrete Element
:具体元素角色实现抽象元素角色提供的 accept() 操作,其方法体通常都是
visitor.visit(this)
,另外具体元素中可能还包含本身业务逻辑的相关操作//具体元素A类 class ConcreteElementA implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationA() { return "具体元素A的操作。"; } } //具体元素B类 class ConcreteElementB implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public String operationB() { return "具体元素B的操作。"; } }
-
Object Structure
:对象结构角色是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现
//对象结构角色 class ObjectStructure { private List<Element> list=new ArrayList<Element>(); public void accept(Visitor visitor) { Iterator<Element> i=list.iterator(); while(i.hasNext()) { ((Element) i.next()).accept(visitor); } } public void add(Element element) { list.add(element); } public void remove(Element element) { list.remove(element); } }
3、模式结构图
4、使用访问者模式改进案例
4.1、代码展示
-
抽象员工类
public interface Employee { /** * 接受一个抽象访问者访问 * @param visitor 访问者 * @return void **/ void accept(Visitor visitor); }
-
具体员工类
-
正式员工
public class FullTimeEmployee implements Employee { /** * 员工姓名 */ private String name; /** * 员工薪水 */ private Double salary; /** * 员工出勤天数 */ private Float attendanceDays; public FullTimeEmployee(String name, Double salary, Float attendanceDays) { this.name = name; this.salary = salary; this.attendanceDays = attendanceDays; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Float getAttendanceDays() { return attendanceDays; } public void setAttendanceDays(Float attendanceDays) { this.attendanceDays = attendanceDays; } @Override public void accept(Visitor visitor) { visitor.visit(this); } }
-
临时工
public class PartTimeEmployee implements Employee { /** * 员工姓名 */ private String name; /** * 员工薪水 */ private Double salary; /** * 员工出勤天数 */ private Float attendanceDays; public PartTimeEmployee(String name, Double salary, Float attendanceDays) { this.name = name; this.salary = salary; this.attendanceDays = attendanceDays; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Float getAttendanceDays() { return attendanceDays; } public void setAttendanceDays(Float attendanceDays) { this.attendanceDays = attendanceDays; } @Override public void accept(Visitor visitor) { visitor.visit(this); } }
-
-
抽象访问者
public interface Visitor { /** * 访问正式员工 * @param fullTimeEmployee 正式员工 * @return void **/ void visit(FullTimeEmployee fullTimeEmployee); /** * 访问临时工 * @param partTimeEmployee 1 * @return void **/ void visit(PartTimeEmployee partTimeEmployee); }
通过多态的方式区别访问不同的员工类型。
-
具体访问类
-
财务部门
public class FinanceVisitor implements Visitor { @Override public void visit(FullTimeEmployee fullTimeEmployee) { System.out.println("正式员工姓名:" + fullTimeEmployee.getName() + ",正式员工薪水:" + fullTimeEmployee.getSalary()); } @Override public void visit(PartTimeEmployee partTimeEmployee) { System.out.println("临时员工姓名:" + partTimeEmployee.getName() + ",临时员工薪水:" + partTimeEmployee.getSalary()); } }
-
人事部门
public class PersonalVisitor implements Visitor { @Override public void visit(FullTimeEmployee fullTimeEmployee) { System.out.println("正式员工姓名:" + fullTimeEmployee.getName() + ",正式员工出勤天数:" + fullTimeEmployee.getAttendanceDays()); } @Override public void visit(PartTimeEmployee partTimeEmployee) { System.out.println("临时员工姓名:" + partTimeEmployee.getName() + ",临时员工出勤天数:" + partTimeEmployee.getAttendanceDays()); } }
-
-
对象结构角色
public class ObjectStructure { private List<Employee> employees = new ArrayList<>(); /** * 添加员工到集合中 * @param employee 1 * @return void **/ public void addEmployee(Employee employee) { employees.add(employee); } /** * 访问用户数据(具体的访问细节由传递来的访问者决定) * @param visitor 1 * @return void **/ public void handle(Visitor visitor) { for (Employee employee : employees) { //每个员工接受访问者访问 employee.accept(visitor); } } }
4.2、客户端测试
public static void main(String[] args) {
//创建员工基础信息
ObjectStructure structure = new ObjectStructure();
structure.addEmployee(new FullTimeEmployee("张三", 5000d, 22.5f));
structure.addEmployee(new PartTimeEmployee("李四", 3500d, 23.5f));
structure.addEmployee(new FullTimeEmployee("王五", 3200d, 22f));
structure.addEmployee(new PartTimeEmployee("赵六", 3200d, 22f));
structure.addEmployee(new PartTimeEmployee("王气", 6200d, 22f));
structure.addEmployee(new FullTimeEmployee("刘八", 7400d, 22f));
System.out.println("财务部访问员工数据...");
structure.handle(new FinanceVisitor());
System.out.println("-------------------------------------");
System.out.println("人事部访问员工数据...");
structure.handle(new PersonalVisitor());
}
输出
财务部访问员工数据...
正式员工姓名:张三,正式员工薪水:5000.0
临时员工姓名:李四,临时员工薪水:3500.0
正式员工姓名:王五,正式员工薪水:3200.0
临时员工姓名:赵六,临时员工薪水:3200.0
临时员工姓名:王气,临时员工薪水:6200.0
正式员工姓名:刘八,正式员工薪水:7400.0
-------------------------------------
人事部访问员工数据...
正式员工姓名:张三,正式员工出勤天数:22.5
临时员工姓名:李四,临时员工出勤天数:23.5
正式员工姓名:王五,正式员工出勤天数:22.0
临时员工姓名:赵六,临时员工出勤天数:22.0
临时员工姓名:王气,临时员工出勤天数:22.0
正式员工姓名:刘八,正式员工出勤天数:22.0
5、优缺点
- 优点
- 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
- 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作
- 缺点
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
- 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问
6、使用场景
- 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作