访问者模式(Visitor)

240 阅读4分钟

定义

表示一个作用于某对象结构中的各元素的操作,他使你可以在不改变各元素的类的前提下定义作用与这些元素的新操作。

本质

预留通路,回调实现

登场角色

  • Visitor(访问者)

    接口或者抽象类,他定义了对每一个元素访问的行为,它的参数就是可以访问的元素。他的方法个数理论上来讲与元素个数一样的,因此访问者模式要求元素的类族要稳定,如果经常需要添加、移除元素,必然会导致频繁的修改Visitor接口,说明不适合使用访问者模式。

  • ConcreteVisitor(具体的访问者)

    具体的访问者,他需要给出对每一个元素类访问时所产生的具体行为。

  • Element(元素)

    元素接口或者抽象类,它定义了一个接受访问者的方法,其意义是指每一个元素都要可以被访问者访问。

  • ConcreteElement

    具体的元素,提供接受访问者方法的具体实现

  • ObjectStructure(对象结构)

    定义当中提到的对象结构,对象结构是一个抽象表达,它内部管理了元素集合,并且可以迭代这些元素访问者访问。

示例代码

/**
 * Element 元素抽象类
 */
public abstract class Staff {
    public String name;
    public int kpi;
    public Staff(String name) {
        this.name = name;
        kpi = new Random().nextInt(10);
    }
    /**
     * 接受Visitor的访问
     * @param visitor
     */
    public abstract void accept(Visitor visitor);
}

/**
 * 具体的元素,工程师
 */
public class Engineer extends Staff{
    public Engineer(String name) {
        super(name);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    /**
     * 工程师这一年的代码量
     * @return
     */
    public int getCodeLines(){
        return new Random().nextInt(10000);
    }
}

/**
 * 具体的元素,经理
 */
public class Manager extends Staff{
    public Manager(String name) {
        super(name);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    /**
     * 一年内做的产品的数量
     * @return
     */
    public int getProducts(){
        return new Random().nextInt(100);
    }
}

public class BusinessReport {
    public List<Staff> mStaffs = new ArrayList<>();

    public BusinessReport() {
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Engineer("工程师1"));
        mStaffs.add(new Engineer("工程师2"));
    }

    /**
     * 为访问者展示报表
     */
    public void showReports(Visitor visitor){
        for (Staff staff: mStaffs){
            staff.accept(visitor);
        }
    }
}

/**
 * CEO Visitor
 * 只关注工程师的KPI、经理的KPI以及新产品数量
 */
public class CEOVisitor implements Visitor{
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师 : " + engineer.name + ", KPI : " + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理 : " + manager.name + ", KPI : " + manager.kpi + ",新产品数量:" + manager.getProducts());
    }
}


/**
 * CTO访问者
 * 只关注工程师的代码行数和经理的产品数量
 */
public class CTOVisitor implements Visitor{
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师 : " + engineer.name + ", 代码行数 : " + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理 : " + manager.name + ", 产品数量 : " + manager.getProducts());
    }
}


/**
 * 测试
 */
public class Client {
    public static void main(String[] args){
        BusinessReport businessReport = new BusinessReport();
        System.out.println("======给CEO看的报表=======");
        businessReport.showReports(new CEOVisitor());
        System.out.println("======给CTO看的报表=======");
        businessReport.showReports(new CTOVisitor());
    }
}

运行结果

======给CEO看的报表=======
经理  王经理, KPI : 9,新产品数量:6
工程师  工程师1, KPI : 0
工程师  工程师2, KPI : 9
======给CTO看的报表=======
经理  王经理, 产品数量 : 43
工程师  工程师1, 代码行数 : 9902
工程师  工程师2, 代码行数 : 5611

功能

访问者模式能给一系列对象透明的添加新功能,从而避免在维护期间对这一系列对象进行修改,而且还能变相实现复用访问者所具有的功能。

优点

  • 好的扩展性,能够在不修改对象结构的前提下,为对象结构中的元素添加新功能。
  • 好的复用性,可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  • 分离无关行为,可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

缺点

  • 对象结构变化困难,不是英语对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。
  • 破坏封装,访问者模式通常需要对象开放内部数据给访问者和对象结构,这破坏了对象的封装性。

使用场景

  • 如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式。
  • 如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作,为了避免这些操作使类变得杂乱,可以使用访问者模式,把这些操作分散到不同的访问者对象中区,使每个访问者对象实现同一类功能。
  • 如果对象结构很少变动,但是需要经常给对象中的元素对象定义新的操作,可以使用访问者模式。