时长:1h
定义:
访问者模式【visitor Pattern】,是一种将数据结构与数据操作分离设计模式。是指
封装一些作用于某种数据结构中的各元素的操作。
特征:
可以在不改变数据结构的前提下定义作用于这些元素的新操作。
属于行为型模式。
说明:
访问者模式,被称为最复杂的设计模式。运用并不多。
1.参与KPI考核的人员
KPI的考核标准,一般是固定不变的,但参与KPI考核的员工会经常变化。
kpi考核打分的人也会经常变化,
2.餐厅就餐人员
餐厅吃饭,餐厅的菜单是基本稳定的,就餐人员基本每天都在变化。就餐人员就是一个访问者。
总结:
访问者,好像就是变化的元素,与不变的结构【标准,规则】的构成关系处理角色。
访问者模式很少能用到,一旦需要使用,涉及到的系统往往比较复杂。
1.数据结构稳定,作用于数据结构的操作经常变化的场景。
2.需要数据结构与数据操作分离的场景。
3.需要对不同数据类型(元素) 进行操作,而不使用分支判断具体类型的场景。

package com.wf.visitor.demo.kpi; import java.util.Random; /** * @ClassName Employee * @Description 员工,元素抽象 * @Author wf * @Date 2020/6/24 10:38 * @Version 1.0 */ public abstract class Employee { private String name; private int kpi; public Employee(String name) { this.name = name; this.kpi = new Random().nextInt(10); } public abstract void accept(IVisitor visitor); public String getName() { return name; } public int getKpi() { return kpi; } }
package com.wf.visitor.demo.kpi; import java.util.Random; /** * @ClassName Engineer * @Description 普通开发人员 * @Author wf * @Date 2020/6/24 10:43 * @Version 1.0 */ public class Engineer extends Employee{ public Engineer(String name) { super(name); } @Override public void accept(IVisitor visitor) { visitor.visit(this); } //考核:代码量 public int getCodingLine(){ return new Random().nextInt(100000); } } package com.wf.visitor.demo.kpi; import java.util.Random; /** * @ClassName Manager * @Description 项目经理 * @Author wf * @Date 2020/6/24 10:44 * @Version 1.0 */ public class Manager extends Employee { public Manager(String name) { super(name); } @Override public void accept(IVisitor visitor) { visitor.visit(this); } //考核:每年的新产品研发数量 public int getProducts(){ return new Random().nextInt(10); } }
package com.wf.visitor.demo.kpi; /** * @ClassName IVisitor * @Description 访问者接口 * @Author wf * @Date 2020/6/24 10:41 * @Version 1.0 */ public interface IVisitor { //传参具体的元素 void visit(Engineer engineer); void visit(Manager manager); } package com.wf.visitor.demo.kpi; /** * @ClassName CTOVisitor * @Description ceo考核者,只有看kpi打分就行 * @Author wf * @Date 2020/6/24 10:49 * @Version 1.0 */ public class CEOVisitor implements IVisitor{ @Override public void visit(Engineer engineer) { System.out.println("工程师:"+engineer.getName()+" ,KPI:"+engineer.getKpi()); } @Override public void visit(Manager manager) { System.out.println("项目经理:"+manager.getName()+" ,KPI:"+manager.getKpi()); } } package com.wf.visitor.demo.kpi; /** * @ClassName CTOVisitor * @Description cto考核者 * @Author wf * @Date 2020/6/24 10:49 * @Version 1.0 */ public class CTOVisitor implements IVisitor{ @Override public void visit(Engineer engineer) { System.out.println("工程师:"+engineer.getName()+" ,编写代码行数:"+engineer.getCodingLine()); } @Override public void visit(Manager manager) { System.out.println("项目经理:"+manager.getName()+" ,产品数量:"+manager.getProducts()); } }
package com.wf.visitor.demo.kpi; import java.util.LinkedList; import java.util.List; /** * @ClassName BusinessReport * @Description 业务报表,数据结构 * @Author wf * @Date 2020/6/24 10:55 * @Version 1.0 */ public class BusinessReport { private List<Employee> employeeList = new LinkedList<>(); public BusinessReport() { employeeList.add(new Manager("项目经理A")); employeeList.add(new Manager("项目经理B")); employeeList.add(new Engineer("程序员A")); employeeList.add(new Engineer("程序员B")); employeeList.add(new Engineer("程序员C")); } public void showReport(IVisitor visitor){ for (Employee employee : employeeList) { employee.accept(visitor); } } }
package com.wf.visitor.demo.kpi; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/6/24 10:54 * @Version 1.0 */ public class Test { public static void main(String[] args) { BusinessReport report = new BusinessReport(); System.out.println("===========CEO看报表==============="); report.showReport(new CEOVisitor()); System.out.println("===========CTO看报表==============="); report.showReport(new CTOVisitor()); } }
测试结果:

说明:
访问者顶层接口定义时,内部会定义visit重载方法,针对不同的访问元素实现子类进行重载。
为什么不设计成一个方法呢?
因为这里一个方法,把具体元素关联起来。
当系统需要增加元素实现子类时,只需要增加一个实现子类,该接口中增加一个重载方法。
系统方便扩展。
java中静态分派,和动态分派。还有双分派。
Java中分派,是方法重载的一种特殊形式。即重载方法,方法名相同,参数个数相同,类型不同的形式。
package com.wf.visitor.dispatch; /** * @ClassName Main * @Description 测试静态分派 * @Author wf * @Date 2020/6/24 11:20 * @Version 1.0 */ public class Main { public static void main(String[] args) { String str = "1"; Integer integer = 1; Main main = new Main(); main.test(integer); main.test(str); } public void test(String str){ System.out.println("String "+str); } public void test(Integer integer){ System.out.println("Integer "+integer); } }
说明:
上面测试代码中,test方法存在两个重载方法,参数个数相同,类型不同,
在编译阶段,就能清楚地知道参数类型,称为静态分派。
相同方法名,不同类型的不同方法,这种形式也称为多分派。
在程序编译阶段,不能明确是哪种类型,只有在运行时,才能知道是哪个类型,
称为动态分派。
package com.wf.visitor.dispatch.dynamic; /** * @ClassName Person * @Description 接口定义 * @Author wf * @Date 2020/6/24 11:33 * @Version 1.0 */ public interface Person { void test(); } package com.wf.visitor.dispatch.dynamic; /** * @ClassName Women * @Description 女人 * @Author wf * @Date 2020/6/24 11:35 * @Version 1.0 */ public class Women implements Person{ @Override public void test() { System.out.println("女人"); } } package com.wf.visitor.dispatch.dynamic; /** * @ClassName Man * @Description 男人 * @Author wf * @Date 2020/6/24 11:34 * @Version 1.0 */ public class Man implements Person { @Override public void test() { System.out.println("男人"); } }
package com.wf.visitor.dispatch.dynamic; /** * @ClassName Main * @Description 测试类 * @Author wf * @Date 2020/6/24 11:35 * @Version 1.0 */ public class Main { public static void main(String[] args) { Person man = new Man(); Person women = new Women(); man.test(); women.test(); } }
说明:
当在编译期时,man或women并不知道自己是什么类型,只有在运行期,
通过new创建实例时,才能知道具体的类型,所以,是动态分派。
在数据结构中,一般会对集合元素进行遍历处理。如下所示:

可以看到,这里是一次动态分派,调用accept方法,具体的类型要到运行时,才能确定。
而且Employee也是一个抽象接口,类型不能确定。

当进入到一个子类中,如Engineer,accept方法调用visit方法,传参this,也是动态分派。需要到
运行时,才能确定类型。【因为需要在运行时创建this实例】

FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException;
FileVisitor接口中定义visitFile方法。传参BasicFileAtributes也是一个接口。
public void visitBeanDefinition(BeanDefinition beanDefinition) { visitParentName(beanDefinition); visitBeanClassName(beanDefinition); visitFactoryBeanName(beanDefinition); visitFactoryMethodName(beanDefinition); visitScope(beanDefinition); if (beanDefinition.hasPropertyValues()) { visitPropertyValues(beanDefinition.getPropertyValues()); } if (beanDefinition.hasConstructorArgumentValues()) { ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); visitIndexedArgumentValues(cas.getIndexedArgumentValues()); visitGenericArgumentValues(cas.getGenericArgumentValues()); } }
访问时,并未改变其中的内容,只是返回相应结果。把数据操作与结构进行分离。
优点:
1.解耦数据结构与数据操作,使用操作集合可以独立变化
2.扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作
3.元素具体类型并非单一,访问者均可操作
4.各角色职责分离,符合单一职责原则。
缺点:
1.无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,
则访问者类必须增加对应元素的操作,违背开闭原则。
2.具体元素变更困难:具体元素的增加属性,删除属性等操作会导致对应访问者类需要
相应的修改,尤其有大量访问者类时,修改范围太大。
3.违背依赖倒置原则:为了达到”区别对待“,访问者依赖的是具体元素类型,而不是抽象。
原文:https://www.cnblogs.com/wfdespace/p/13187201.html