引言
在软件开发中,有时候需要处理集合对象结构,在该对象结构中存储了多个不同类型的对象信息,对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式,还有可能增加新的处理方式。这就是访问者模式要解决的问题。
1.概念
访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
2.模式结构
3.模式分析
Vistor:抽象访问者,为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。核心代码如下:
public abstract class Visitor {
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
public void visit(ConcreteElementC elementC){
//元素ConcreteElementC操作代码
}
}
ConcreteVisitor:具体访问者,实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。核心代码如下:
class ConcreteVisitor extends Visitor {
public void visit(ConcreteElementA elementA){
//元素ConcreteElementA操作代码
}
public void visit(ConcreteElementB elementB){
//元素ConcreteElementB操作代码
}
}
Element:抽象元素,一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。核心代码如下:
public interface Element {
public void accept(Visitor visitor);
}
ConcreteElement:具体元素,实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。核心代码如下:
class ConcreteElementA implements Element {
public void accept(Visitor visitor){
visitor.visit(this);
}
public void operationA(){
//业务方法
}
}
ObjectStructure:对象结构,是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。核心代码如下:
class ObjectStructure {
private ArrayList<Element> list = new ArrayList<>();
public void accept(Visitor visitor){
for (Element element : list) {
element.accept(visitor);
}
}
public void addElement(Element element){
list.add(element);
}
public void removeElement(Element element){
list.remove(element);
}
}
4.具体实例分析
EmployeeElement:抽象元素,是一个接口,表示员工,声明了公有的接受访问的方法。具体代码如下:
//抽象元素接口:表示抽象的员工
public interface EmployeeElement {
public void accept(DepartmentVisitor departmentVisitor);//接受来自抽象访问者的访问
}
FullTimeEmployeeElement:具体元素类,实现EmployeeElement接口,表示全职工,全职工除了姓名、工作时间、薪资属性和get、set方法外,还实现了访问方法。具体代码如下:
//具体元素类:全职工
class FullTimeEmployeeElement implements EmployeeElement {
private String name;//员工姓名
private int workTime;//工作时间
private double wage;//薪水
public FullTimeEmployeeElement(String name, int workTime, double wage) {
this.name = name;
this.workTime = workTime;
this.wage = wage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getWorkTime() {
return workTime;
}
public void setWorkTime(int workTime) {
this.workTime = workTime;
}
public double getWage() {
return wage;
}
public void setWage(double wage) {
this.wage = wage;
}
public void accept(DepartmentVisitor departmentVisitor){
departmentVisitor.visit(this);
}
}
PartTimeEmployeeElement:具体元素类,实现EmployeeElement接口,表示临时工,临时工除了姓名、工作时间、薪资属性和get、set方法外,还实现了访问方法。具体代码如下:
//具体元素类:临时工
class PartTimeEmployeeElement implements EmployeeElement {
private String name;//员工姓名
private int workTime;//工作时间
private double wage;//薪水
public PartTimeEmployeeElement(String name, int workTime, double wage) {
this.name = name;
this.workTime = workTime;
this.wage = wage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getWorkTime() {
return workTime;
}
public void setWorkTime(int workTime) {
this.workTime = workTime;
}
public double getWage() {
return wage;
}
public void setWage(double wage) {
this.wage = wage;
}
public void accept(DepartmentVisitor departmentVisitor){
departmentVisitor.visit(this);
}
}
DepartmentVisitor:抽象访问者,表示访问员工的部门。具体代码如下:
//抽象访问者:部门类
public abstract class DepartmentVisitor {
public abstract void visit(FullTimeEmployeeElement fullTimeEmployeeElement);
public abstract void visit(PartTimeEmployeeElement partTimeEmployeeElement);
}
FinanceDepartmentVisitor:具体访问者,继承抽象访问者,表示财务部,分别对全职工和小时工进行工资的计算。具体代码如下:
//具体访问者:财务部
class FinanceDepartmentVisitor extends DepartmentVisitor {
//财务部对全职工的访问
public void visit(FullTimeEmployeeElement fullTimeEmployeeElement){
int time = fullTimeEmployeeElement.getWorkTime();
double wage = fullTimeEmployeeElement.getWage();
//如果工作时间大于200小时,则工资=基础工资+超过部分*10
if(time > 200){
wage = wage + (time - 200) * 10;
}else if(time < 200 && time > 100){
//如果工作时间大于100小时,则工资=基础工资+超过部分*5
wage = wage + (time - 100) * 5;
}else{
//工作时间过少,不发工资
wage = 0;
}
System.out.println("员工: " + fullTimeEmployeeElement.getName() + " 的工资为:" + wage);
}
//财务部对小时工的访问
public void visit(PartTimeEmployeeElement partTimeEmployeeElement){
int time = partTimeEmployeeElement.getWorkTime();
double wage = partTimeEmployeeElement.getWage();
wage = wage + time * 20;
System.out.println("员工: " + partTimeEmployeeElement.getName() + " 的工资为:" + wage);
}
}
HRDepartmentVisitor:具体访问者,继承抽象访问者,表示人事部,分别对全职工和小时工进行请假时长的计算。具体代码如下:
//具体访问者:人事部
public class HRDepartmentVisitor extends DepartmentVisitor{
//人事部对全职工的访问
public void visit(FullTimeEmployeeElement fullTimeEmployeeElement){
int time = fullTimeEmployeeElement.getWorkTime();
int freeTime;
//如果工作时间大于150小时,则未请假
if(time >= 150){
freeTime = 0;
}else{
freeTime = 150 - time;
}
System.out.println("员工: " + fullTimeEmployeeElement.getName() + " 的请假时长:" + freeTime + "h");
}
//人事部对小时工的访问
public void visit(PartTimeEmployeeElement partTimeEmployeeElement){
System.out.println("员工: " + partTimeEmployeeElement.getName() + " 的请假时长为0h");
}
}
ObjectStructure:对象结构,表示公司,将访问者和对象元素统一起来,接受不同的访问者实现对不同的对象元素的访问。具体代码如下:
//公司对象结构
class ObjectStructure {
private ArrayList<EmployeeElement> list = new ArrayList<>();
public void accept(DepartmentVisitor departmentVisitor){
for (EmployeeElement element : list) {
element.accept(departmentVisitor);
}
}
public void addElement(EmployeeElement element){
list.add(element);
}
public void removeElement(EmployeeElement element){
list.remove(element);
}
}
Client:客户端,模拟实现了不同部门对不同员工的访问。具体代码如下:
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
EmployeeElement employee1 = new FullTimeEmployeeElement("李华",210,5000);
EmployeeElement employee2 = new PartTimeEmployeeElement("小明",50,0);
objectStructure.addElement(employee1);
objectStructure.addElement(employee2);
objectStructure.accept(new FinanceDepartmentVisitor());
objectStructure.accept(new HRDepartmentVisitor());
}
}
运行代码,结果如下:
5.优缺点
主要优点如下:
(1)增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
(3)让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。
主要缺点如下:
(1)增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
(2)破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。
6.适用情况
(1)一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
(2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
(3)对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。