设计模式学习笔记(19)访问者

本文实例代码:https://github.com/JamesZBL/java_design_patterns

在访问者(Visitor)模式中,客户通过一个访问者类,将对于一个数据结构的不同元素的访问方法封装起来,这样,对一类元素的访问方法可以通过在访问者中修改逻辑而实现,这种类型的设计模式属于行为型模式。

如果对一个数据结构的访问欲获得的结果,需要该结构内部属性经过一系列计算才能得到,那么一般情况下,我们会将计算过程写到数据结构中,用一个统一的接口约束数据结构的计算方法,保证都能获得一致的结构。但是如果有新的计算功能需求,就要对数据结构类进行修改,显然,这不符合开闭原则。所以我们可以将对不同数据结构中的属性的计算方法抽象到一个访问者类中。这样,当需要修改算法的时候只需修改访问者中的具体逻辑就可以了,一定程度上将数据结构和算法分离开来了。

实例

现在假设公司的领导要到各个部门视察,领导每次视察的时候都可能提出不同的问题,需要部门的主管以及员工等做不同的整改工作。我们将部门抽象成 Unit 类,将领导抽象成 Leader 类,每次领导视察过后,部门中都要对工作方式和计划做一些调整,这时就要修改 Unit 类了,使新的类能够对领导新提出的需求做出合理的响应。如果领导每次来都会在不同的方面提出意见,那么就要频繁的对 Unit 类进行修改。为了避免这些繁琐的修改工作,我们可以用一个新的 UnitVisitor 类来替代 Leader 类。 UnitVisitor 类中封装了对不同部门的访问方法,这时我们将 Unit 类进一步抽象,抽象出一个独立的接口,接口里包含被 UnitVisitor 类访问的方法。

Unit.java

public abstract class Unit {

  // 子部门
  private Unit[] children;

  // 由于子部门可以有多个,也可以没有,所以这里使用可变长参数
  public Unit(Unit... children) {
    this.children = children;
  }

  /**
   * 接受访问
   */
  public void beVisited(UnitVisitor visitor) {
    for (Unit childUnit : children) {
      childUnit.beVisited(visitor);
    }
  }
}

一个部门可以有若干个子部门,访问者访问某个部门的时候也会同时访问其所有的子部门,这样一直递归访问下去,直到访问的子部门没有下属部门为止。

现在出现了三种被访问对象,他们分别是 Boss 主管, Enginner 工程师和 Mangager 经理,这三种角色之间存在直接的领导和被领导的关系。访问者对三种角色进行访问时获得的响应也是不同的。

Boss.java

public class Boss extends Unit {

  public Boss(Unit... children) {
    super(children);
  }

  @Override
  public void beVisited(UnitVisitor visitor) {
    visitor.visitBoss(this);
    super.beVisited(visitor);
  }

  @Override
  public String toString() {
    return "老板";
  }
}

Mangager.java

public class Manager extends Unit {

  public Manager(Unit... children) {
    super(children);
  }

  @Override
  public void beVisited(UnitVisitor visitor) {
    visitor.visitManager(this);
    super.beVisited(visitor);
  }

  @Override
  public String toString() {
    return "经理";
  }
}

Engineer.java

public class Engineer extends Unit {

  public Engineer(Unit... children) {
    super(children);
  }

  @Override
  public void beVisited(UnitVisitor visitor) {
    visitor.visitEngineer(this);
    super.beVisited(visitor);
  }

  @Override
  public String toString() {
    return "工程师";
  }
}

不难看出,每个角色的访问方法中都调用了作为参数的访问者的某个访问方法,这样写的好处是可以将访问方法和具体的被访问者解耦,至于如何被访问以及该提供哪些内容,被访问者其实并不不需要关心。

接下来就要写访问者接口了,这个接口中囊括了适配所有不同被访问者类的访问方法,接口的实现类只需根据自身的需要重写某个或某几个方法即可。

UnitVisitor.java

public interface UnitVisitor {

  void visitEngineer(Engineer engineer);

  void visitBoss(Boss boss);

  void visitManager(Manager manager);
}

关于三种角色:主管、经理和工程师,也分别有不同的访问者,他们分别是 BossVisitorManagerVisitorEngineerVisitor,它们都实现 Visitor 接口。

BossVisitor.java

public class BossVisitor implements UnitVisitor {

  private static final Logger LOGGER = LoggerFactory.getLogger(BossVisitor.class);

  @Override
  public void visitEngineer(Engineer engineer) {

  }

  @Override
  public void visitBoss(Boss boss) {
    LOGGER.info("你好,{}", boss);
  }

  @Override
  public void visitManager(Manager manager) {

  }
}

ManagerVisitor.java

public class ManagerVisitor implements UnitVisitor {

  private static final Logger LOGGER = LoggerFactory.getLogger(ManagerVisitor.class);

  @Override
  public void visitEngineer(Engineer engineer) {

  }

  @Override
  public void visitBoss(Boss boss) {

  }

  @Override
  public void visitManager(Manager manager) {
    LOGGER.info("你好,{}", manager);
  }
}

EngineerVisitor.java

public class EngineerVisitor implements UnitVisitor {

  private static final Logger LOGGER = LoggerFactory.getLogger(EngineerVisitor.class);

  @Override
  public void visitEngineer(Engineer engineer) {
    LOGGER.info("你好,{}", engineer);
  }

  @Override
  public void visitBoss(Boss boss) {

  }

  @Override
  public void visitManager(Manager manager) {

  }
}

现在来模拟一下三种访问者访问对三种角色的访问场景,一个部门主管领导两个经理,每个经理分别领导两名工程师,主管及其各级下属依次被访问

App.java

public class Application {

  public static void main(String[] args) {
    Boss boss = new Boss(new Manager(new Engineer(), new Engineer(), new Engineer()), new Manager(new Engineer(), new Engineer()), new Manager(new Engineer()));
    boss.beVisited(new BossVisitor());
    boss.beVisited(new ManagerVisitor());
    boss.beVisited(new EngineerVisitor());
  }
}

总结

上面实例中的三种角色就是三种不同的数据结构,它们分别约定了访问者对自己的访问方式,访问者根据被访问者的类型实现不同的访问算法。

所以,访问者模式主要解决的问题是稳定的数据结构和易变的操作耦合问题,它将数据结构和数据操作分离开来。如果要对一个对象结构中的元素进行很多不同的且较为复杂的操作,而应当尽量避免让这些操作掺杂在这些对象的类中,可以使用访问者模式将这些封装到访问者的类中。实现方式为:使被访问的类实现统一的为访问者提供“接待”服务的接口,每个被访问者接待方法中将自身的引用作为参数传递给访问者。借助访问者模式,数据结构和数据操作很好的分离开来了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.