访问者模式
**概述:**提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
上述代码中的handle对不同类型的员工和不同的部分做了不同的处理,满足了员工数据汇总的要求,但是该解决方案存在如下问题
(1)EmployeeList类非常庞大,代码相当冗长,承担了大量的职责,不方便代码的复用,不利于系统的扩展,违背了单一职责。
(2)代码存在大量的if…else,导致测试和维护代码难度较大
(3)违背开闭原则,如果要新增一个部门或者新增一个不同类员工,处理不同的员工集合,则要修改源代码。
(1)Vistor(抽象访问者)抽象访问者为对象结构中的每一个具体元素类(ConcreteElement)声明一个访问操作 vistitConcreteElementX,根据入参可以清楚的知道需要访问的具体元素的类型,具体访问者需要实现这些方法,定义对这些元素的访问操作。
(2)ConcreteVisitor(具体访问者)具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
(3)Element(抽象元素)抽象元素一般是抽象类或者接口,它定义一个accept方法,该方法通常以一个抽象访问者作为参数(稍后解释为什么这样)
(4)ConcreteElement(具体元素)实现了抽象元素的accept方法,在方法中调用访问者的访问方法以便完成一个元素的操作。
(5)ObjectStructure(对象结构)对象结构是一个元素的集合,用于存放元素的对象,并且提供了遍历该内部元素的方法。可以集合组合模式来实现,也可以是一个简单的集合对象,例如是一个List或者是Set对象
对象结构存储了不同类型的元素对象,以供不同的访问者访问,访问者模式也包含了两个层次结构。
- 访问者层次结构,提供了抽象访问者和具体访问者
- 元素层次结构,提供了抽象元素和具体元素
相同的访问者可以访问不同的元素,相同的元素可以接受不同的访问者访问。在访问者模式中,增加新的访问者无须修改源代码
/**
* 抽象访问者
*/
public abstract class Visitor {
abstract void visit(ConcreteElementA concreteElementA);
abstract void visit(ConcreteElementB concreteElementA);
public void visit(ConcreteElementC concreteElementC){
}
}
/**
* 抽象元素类
*/
class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象元素类
*/
class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象元素类
*/
class ConcreteElementC implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
对于元素类而言,抽象元素类一般定义了一个accept方法,用于接受访问者的访问,典型代码如下
/**
* 抽象元素
*
*/
public interface Element {
void accept(Visitor visitor);
}
需要注意的是accept方法的入参是一个抽象访问者,是针对抽象抽象访问者编程,在运行时确定其具体类,调用具体访问者对象的visit方法,实现对元素的操作,前面说到了,访问者类的visit的入参是一个具体元素,所以在这里,我们将具体元素当前对象(this)传入visit方法中,这种调用机制就是双重分派,其典型代码如下
package com.learn.designmode.mode.visitor;
/**
* 抽象元素
*
*/
public interface Element {
void accept(Visitor visitor);
}
/**
* 抽象元素类
*/
class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象元素类
*/
class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象元素类
*/
class ConcreteElementC implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
具体元素类A(){
方法(抽象访问类){
访问类.处理方法(本类)
}
}
具体访问类(){
处理方法(元素类A){
//对元素a的处理
}
}
对象结构类(){
具体元素类集合
处理方法(抽象访问类){
for(){
具体元素.方法(访问类)
}
}
}
————————————————
版权声明:本文为CSDN博主「rgbhi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/rgbhi/article/details/107154618
对象结构
package com.learn.designmode.mode.visitor;
import lombok.Data;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@Data
public class ObjectStructure {
/**
* 定义一个集合用于存储元素集合
*/
private List<Element> list = new ArrayList<Element>(10);
public void accept(Visitor visitor){
// 使用迭代器对集合存储的元素对象进行访问,并逐个嗲用每一个对象的accept方法
Iterator i = list.iterator();
while (i.hasNext()){
// 遍历访问集合中的每一个元素
((Element)(i.next())).accept(visitor);
}
}
public void addElement(Element element){
list.add(element);
}
public void removeElement(Element element){
list.remove(element);
}
}
答:新增访问者类型,符合开闭原则,只要新增的访问者类实现对现有的具体类的处理即可。增加新的元素,不符合开闭原则,需要对现有的所有的访问者类型都增加对新增元素的处理。
完整的解决方案
package com.learn.designmode.mode.visitor.demo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 员工类:抽象元素类
*/
public interface Employee {
/**
* 接受访问者访问
*/
public void visit(Department department);
}
/**
* 全职员工类:具体元素类
*/
@Data
class FullTimeEmplyee implements Employee{
/**
* 姓名
*/
private String name;
/**
* 员工周薪
*/
private BigDecimal weeklyWage;
/**
* 工作时间
*/
private int workTime;
FullTimeEmplyee(String name,BigDecimal weeklyWage,int workTime) {
this.name = name;
this.weeklyWage = weeklyWage;
this.workTime = workTime;
}
@Override
public void visit(Department department) {
department.visit(this);
}
}
/**
* 兼职员工类:具体元素类
*/
@Data
class PartTimeEmplyee implements Employee{
/**
* 姓名
*/
private String name;
/**
* 员工周薪
*/
private BigDecimal weeklyWage;
/**
* 工作时间
*/
private int workTime;
PartTimeEmplyee(String name,BigDecimal weeklyWage,int workTime) {
this.name = name;
this.weeklyWage = weeklyWage;
this.workTime = workTime;
}
@Override
public void visit(Department department) {
department.visit(this);
}
}
package com.learn.designmode.mode.visitor.demo;
import java.math.BigDecimal;
/**
* 部门类:抽象访问者类
*/
public abstract class Department {
// 声明一组重载的访问方法,用于访问不同类型的具体元素
public abstract void visit(FullTimeEmplyee fullTimeEmplyee);
public abstract void visit(PartTimeEmplyee partTimeEmplyee);
}
/**
* 财务部:具体访问者
*/
class FADDepartment extends Department{
@Override
public void visit(FullTimeEmplyee fullTimeEmplyee) {
int workTime = fullTimeEmplyee.getWorkTime();
BigDecimal weekWage = fullTimeEmplyee.getWeeklyWage();
// 如果周时大于40,多余的按100算
if (workTime > 40){
weekWage = weekWage.add (new BigDecimal(workTime -40).multiply(new BigDecimal(100)));
}else if (workTime < 40){
weekWage = weekWage.subtract(new BigDecimal(40 -workTime).multiply(new BigDecimal(100)));
if (weekWage.compareTo(new BigDecimal(0)) <0){
weekWage = new BigDecimal(0);
}
}
System.out.println("正式员工 " + fullTimeEmplyee.getName() + "实际工资为" +weekWage);
}
@Override
public void visit(PartTimeEmplyee partTimeEmplyee) {
int workTime = partTimeEmplyee.getWorkTime();
BigDecimal weekWage = partTimeEmplyee.getWeeklyWage();
System.out.println("临时工 " + partTimeEmplyee.getName() + "实际工资为" +weekWage);
}
}
/**
* 人力资源部:具体访问者
*/
class HRDepartment extends Department{
@Override
public void visit(FullTimeEmplyee fullTimeEmplyee) {
int workTime = fullTimeEmplyee.getWorkTime();
System.out.println("正式员工" + fullTimeEmplyee.getName() + "实际工作时间为" + workTime +"小时");
// 如果周时大于40,多余的按100算
if (workTime > 40){
System.out.println("正式员工" + fullTimeEmplyee.getName() + "加班时间为" + (workTime - 40) +"小时");
}else if (workTime < 40){
System.out.println("正式员工" + fullTimeEmplyee.getName() + "请假时间为" + (40 - workTime) +"小时");
}
}
@Override
public void visit(PartTimeEmplyee partTimeEmplyee) {
int workTime = partTimeEmplyee.getWorkTime();
System.out.println("正式员工" + partTimeEmplyee.getName() + "实际工作时间为" + workTime +"小时");
}
}
package com.learn.designmode.mode.visitor.demo;
import java.util.ArrayList;
import java.util.List;
public class EmployeeList {
// 定义一个集合用于存储员工集合
private List<Employee> list = new ArrayList<>();
public void addEmployee(Employee employee){
list.add(employee);
}
// 遍历访问员工集合中的每一个员工对象
public void accept(Department department){
for (Employee item:list){
item.visit(department);
}
}
}
package com.learn.designmode.mode.visitor.demo;
import java.math.BigDecimal;
public class Client {
public static void main(String[] args) {
EmployeeList list = new EmployeeList();
Employee employee1,employee2,employee3,employee4,employee5;
employee1 = new FullTimeEmplyee("zlx1",new BigDecimal(1000),50);
employee2 = new FullTimeEmplyee("zlx2",new BigDecimal(1000),40);
employee3 = new FullTimeEmplyee("zlx3",new BigDecimal(1000),40);
employee4 = new PartTimeEmplyee("zlx4",new BigDecimal(1000),40);
employee5 = new PartTimeEmplyee("zlx5",new BigDecimal(1000),40);
list.addEmployee(employee1);
list.addEmployee(employee2);
list.addEmployee(employee3);
list.addEmployee(employee4);
list.addEmployee(employee4);
list.addEmployee(employee5);
Department department = new FADDepartment();
list.accept(department);
}
}
访问者模式与组合模式联用
总结
优点
(1)增加新的访问很方便,使用访问者模式,增加新的访问操作意味着一个新的具体访问类,符合开闭原则
(2)将有关元素的对象访问集中到一个访问者对象中,而不是分散在一个个元素类中,类的职责更加清晰,有利于对象结构中元素的复用,相同的对象结构可以提供多个不同的访问者模式
(3)让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。
缺点
(1)增加新的元素类很困难,在访问者模式中,每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的操作,这违背了开闭原则的要求
(2)破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露自己的细节,否则无法提供访问者访问
适用场景
(1)一个对象结构中存在多个类型的对象,希望对这些对象实施一些依赖某具体类型的操作。在访问者中针对每一种具体的类型都提供了一种访问操作,不同类型的对象可以有不同的访问操作。
(2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而且需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类。访问者模式将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离
(3)对象结构中对象对应的类很少改变,但是经常需要在此对象结构上定义新的操作。