访问者模式 - Visitor Pattern

WHAT

定义

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

类图

Visitor 抽象访问者

抽象类或者接口,声明访问者可以访问哪些元素。

public interface IVisitor {
  public void visit(ConcreteElement1 el1);
  public void visit(ConcreteElement2 el2);
}

ConcreteVisitor 具体访问者

影响访问者访问到一个类后该怎么干,要做什么事

public class Visitor implements IVisitor {
  //访问el1元素
  public void visit(ConcreteElement1 el1){
    el1.doSomething();
  }
  //访问el2元素
  public void visit(ConcreteElement2 el2){
    el2.doSomething();
  }
}

Element 抽象元素

接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的

public abstract class Element {
  //定义业务逻辑
  public abstract void doSomething();
  //允许谁来访问
  public abstract void accept(IVisitor visitor);
}

ConcreteElement 具体元素

实现accept方法,通常是visitor.visit(this)

public class ConcreteElement1 extends Element {
  //完善业务逻辑
  public void doSomething(){
    // 业务处理
  }
  // 允许那个访问者访问
  public void accept(IVisitor visitor){
    visitor.visit(this);
  }
}

public class ConcreteElement2 extends Element {
  //完善业务逻辑
  public void doSomething(){
    // 业务处理
  }
  // 允许那个访问者访问
  public void accept(IVisitor visitor){
    visitor.visit(this);
  }
}

ObjectStruture 结构对象

元素产生者,容纳在多个不同类、不同接口的容器

如List、Set、Map等

public class ObjectStruture {
  // 对象生成器,这里通过一个工厂模式模拟
  public static Element createElement(){
    Random rand = new Random();
    if(rand.nextInt(100) > 50){
      return new ConcreteElement1();
    }else {
      return new ConcreteElement2();
    }
  }
}

场景类

public class Client{
  public static void main(String[] args){
    for (int i = 0; i < 10; i ++){
      // 获取元素对象
      Element el = ObjectStruture.createElement();
      //接受访问者访问
      el.accept(new Visitor());
    }
  }
}

WHY

优势

  • 符合单一职责原则

    Element抽象类的子类负责数据的加载,Visitor负责报表的展示,两个职责非常明确地分离开来

  • 优秀的扩展性

  • 灵活性非常高

劣势

  • 具体元素对访问者公布细节
    • 访问者要访问一个类就必然要求这个类公布一些方法和数据
    • 访问者关注了其他类的内部细节,违背迪米特法则
  • 具体元素变更比较困难
    • 具体元素角色的增加、删除、修改都比较困难
  • 违背了依赖倒置原则
    • 范文哲依赖的是具体元素,而不是抽象元素,破坏了依赖倒置原则

HOW

使用场景

业务规则要求遍历多个不同的对象。

需要对一个对象结构中的对象进行很多不同并且不相关的操作,想避免这些操作污染这些对象的类。

扩展

统计功能

需要遍历多个对象,然后针对某一属性进行相加等计算时,通过Element接受visitor,在visitor中进行计算,然后输出。

多个访问者

针对相同的一批数据,需要从多重维度(多个访问者)输出数据,通过增加Visitor的接口,区分不同维度;然后各自实现Visitor,从而实现相同的数据吐出不同维度的数据。

最佳实践

访问者模式是一种集中规整模式,适用于大规模重构的项目,在需求非常清晰,原系统的功能点也已经明确,通过访问者模式可以很容易把一些工鞥呢进行梳理,从而达到功能集中化的目的。

TIPS

迭代器模式只能访问同类或通接口的数据(使用了instanceof,也能访问所有的数据),而访问者模式是对迭代器模式的扩展,可以遍历不同的对象,执行不同的才做


《设计模式之禅》 第25章访问者模式