Java设计模式(二十五)访问者模式

访问者模式

定义

是一种将数据结构与数据操作分离的设计模式。是指封装一些作用于某些数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。属于行为型模式。

访问者模式的基本思想是针对系统中拥有固定类型的对象结构(元素),在其内提供一个accept()方法用来接受访问者的访问,不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同功能的数据结果。accept()方法可以接收不同的访问者对象,然后在内部将自己(元素)转发到接收到的访问者对象的visit()方法内。访问者内部对应类型的visit()方法就会得到回调执行,对元素进行操作。也就是通过两次动态分发(第一次是对访问者的分发accept()方法,第二次是对元素的分发visit()方法)才最终将一个具体的元素传递到一个具体的访问者。如此就解耦了数据结构与操作,且数据操作不会改变元素状态。
访问者的核心是解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性。可以通过扩展不同的数据类型(访问者)实现对相同元素集的不同的操作。

适用情景

当系统中存在类型数目稳定的一类数据结构时,可以通过访问者模式方便地实现对该类型所有数据类型的不同操作,而不会对数据产生任何副作用。简而言之,就是对集合中不同类型的数据进行操作,则使用访问者模式

  1. 数据结构稳定,作用于数据结构的操作经常变化的场景;
  2. 需要数据结构与数据操作分离的场景;
  3. 需要对不同数据类型进行操作,而不适用分支判断具体类型的场景。

角色

  1. 抽象访问者(visitor):接口或抽象类,该类冠以了对每一个具体元素(Element)的访问行为visit()方法,其参数就是具体的元素对象。理论上讲:Visitor的方法个数与元素个数是相等的。如果元素个数经常变动,会导致visitor的方法也要进行变动。
  2. 具体访问者(ConcreteVisitor):实际对具体元素的操作。
  3. 抽象元素(Element):接口或抽象类。定义了一个接受访问者访问的方法accept(),表示所有元素类型都支持别访问者访问。
  4. 具体元素(ConcreteElement):具体元素类型,提供接受访问者的具体实现。通常的实现为visitor.visit(this);
  5. 结构对象(ObjectStruture):该类维护了元素集合,并提供方法接受访问者对该集合所有元素进行操作。

实例

工作绩效:员工分为工程师和经理;管理层友CEO和CTO。CTO关注工程师的代码量,经理的新产品数量;CEO关注的是工程师的KPI和经理的KPI和新产品数量。

public interface IVisitor {
    void visit(Engineer engineer);
    void visit(Manager manager);
}

public class CEOVisitor implements IVisitor {
    public void visit(Engineer engineer) {
        System.out.println(String.format("工程师 %s ,KPI %s", engineer.name, engineer.kpi));
    }

    public void visit(Manager manager) {
        System.out.println(String.format("经理 %s ,KPI %s , 新产品数量 %s", manager.name, manager.kpi, manager.getProducts()));
    }
}

public void visit(Engineer engineer) {
        System.out.println(String.format("工程师: %s ,代码行数: %s", engineer.name, engineer.getCodeLines()));
    }

    public void visit(Manager manager) {
        System.out.println(String.format("经理: %s ,新产品数量: %s", manager.name, manager.getProducts()));

    }
public abstract class Employee {
    public String name;
    public int kpi;

    public Employee(String name) {
        this.name = name;
    }

    public abstract void accept(IVisitor visitor);
}

public class Engineer  extends Employee{


    public Engineer(String name) {
        super(name);
    }

    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }

    public int getCodeLines(){
        return new Random().nextInt(100000);
    }
}

public class Manager extends Employee{
    public Manager(String name) {
        super(name);
    }

    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }

    public int getProducts(){
        return new Random().nextInt(10);
    }
}

public class BusinessReport {
    private List<Employee> employees = new LinkedList<Employee>();

    public BusinessReport() {
        employees.add(new Manager("manager_A"));
        employees.add(new Manager("manager_B"));
        employees.add(new Engineer("engineer_A"));
        employees.add(new Engineer("engineer_B"));
        employees.add(new Engineer("engineer_C"));
    }

    public void showReport(IVisitor visitor) {
        for (Employee employee : employees) {
            employee.accept(visitor);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        BusinessReport report = new BusinessReport();
        report.showReport(new CEOVisitor());

        report.showReport(new CTOVisitor());
    }
}

访问者模式的最大优点就是增加访问者非常容易。

从静态分派到动态分派

变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)例如:
List list = null;
list = new ArrayList();
声明了一个变量list,它的静态类型是List,而它的实际类型是ArrayList。根据对象的类型而对方法进行的选择,就是分派。分派又分为两种,即静态分派和动态分派。

1. 静态分派

静态分派就是按照便令的静态类型进行分派,从而确定方法的执行版本,静态分派在编译十七就尅确定方法的版本。而静态分派的典型应用就是方法重载。

public class Main {
    public void test(String str){
        System.out.println("str");
    }
    public void test(Integer integer){
        System.out.println("integer");
    }

    public static void main(String[] args) {
        String str = "1";
        Integer integer = 1;
        Main main = new Main();
        main.test(str);
        main.test(integer);
    }
}

在静态分派判断的时候,我们根据多个判断依据(参数类型和个数)判断出了方法的版本,这个就是多分派的概念。Java是静态多分派语言。

2. 动态分派

对于动态分派,与静态分派相反,它不是在编译期确定的方法版本,而是在运行时才能确定的。而动态分派典型的应用就是多态的特性。

public interface Person {
    void test();
}
class man implements Person{

    public void test() {
        System.out.println("man");
    }
}

class woman implements Person{

    public void test() {
        System.out.println("woman");
    }
}

class main {
    public static void main(String[] args) {
        Person man =new man();
        Person woman = new woman();
        man.test();
        woman.test();
    }
}

这段程序输出结果为man和woman,然而这里的test()方法版本无法根据Man和Woman的静态类型去半段,他们的静态类型都是Person接口。显然产生的输出结果就是因为test()方法的版本实在运行时判断的这就是动态分派。
动态分派判断的方法实在运行时获取Man和Woman的实际引用类型,在确定方法的版本,而由于判读的依据只是实际引用类型,只有一个判断依据,所以这就是单分派的概念。Java是动态单分派语言。

访问者模式中的伪动态双分配

动态双分派就是在运行时以及两个实际类型去判断一个方法的运行行为,访问者模式实现的手段是进行了两次动态单分派来达到这个效果。

访问者模式在源码中的应用

JDK的NIO模块下的FileVisitor
Spring IOC 中的BeanDefinitionVisitor类

优点

  1. 解耦数据结构与数据操作,使得操作集合可以独立变化;
  2. 扩展性号:可以通过扩展访问者角色,实现对数据集的不同操作;
  3. 元素具有类型并非单一,访问者均为可操作;
  4. 各角色职责分离,符合单一职责原则。

缺点

  1. 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作。违反了开闭原则。
  2. 具体元素变更困难:具体元素增加属性,删除属性等操作会导致队形的访问者类需要进行响应的修改,尤其当有大量访问者类时,修改范围太大。
  3. 违反依赖导致原则:为了达到区别对待,访问者依赖的是具体元素类型,而不是抽象。

你可能感兴趣的:(Java设计模式,java,设计模式)