前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >访问者模式浅析

访问者模式浅析

作者头像
孟君
发布2020-06-15 16:46:34
3370
发布2020-06-15 16:46:34
举报

有的时候,由于访问者所关注的点不同,可以需要对同样的一件事物做出不同的回应和操作。比如:在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量进行划价;而药房工作人员则根据药品名称和数量准备药品;针对同样的处方笺,划价人员和药房工作人员作为处方笺的访问者,其做的操作是不一样的。

一个处方笺如下图所示:

本文今天要讲访问者模式,就是可以使你在不改变各元素的累的前提下,定义作用于这些元素的新操作,从而可以满足不同的访问者访问的场景。

一. 访问者模式的基本介绍

意图

访问者模式是对象的行为模式,其表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

结构

访问者模式的基本结构如下:

这里涉及到的参与者有如下几种:

  • Visitor(访问者)
    • 声明一个或者多个访问操作
  • ConcreteVistor(具体的访问者)
    • 实现Visitor声明的所有接口,也就是访问者所声明的各个访问操作。
  • Element(元素)
    • 定义一个Accept操作,接受一个访问者对象作为一个参数。
  • ConcreteElement(具体元素)
    • 实现Accept操作,该操作以一个访问者为参数
  • ObjectStructure(对象结构)
    • 可以遍历结构中的所有元素
    • 可以提供一个高层的接口以允许该访问它的元素
    • 可以设计成一个复合对象或者一个集合,如一个列表(List)或者集合(Set)

二. 访问者模式的示例

接下来以一个图形(包括圆形、长方形))输出XML和JSON字串来完成一个简单的访问者模式的示例:

  • 元素Shape.java

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public interface Shape {

   void accept(PrintVisitor visitor);
  
}
  • 具体元素素类Circle.java和Rectangle.java
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class Circle implements Shape {

  private int id;
  
  private int x;

  private int y;
  
  private int radius;

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

}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class Rectangle implements Shape {

  private int id;
  
  private int x;

  private int y;
  
  private int width;
  
  private int height;
  
  @Override
  public void accept(PrintVisitor visitor) {
    visitor.visit(this);
  }
  
}
  • 访问者PrintVisitor.java
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public interface PrintVisitor {

  void visit(Circle circle);
  
  void visit(Rectangle rectangle);
}
  • 具体访问者JSONPrintVisitor.java和XMLPrintVisitor.java
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class XMLPrintVisitor implements PrintVisitor {

  @Override
  public void visit(Circle circle) {
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
    sb.append("<circle>").append("\n");
    sb.append("\t").append("<id>").append(circle.getId()).append("</id>").append("\n");
    sb.append("\t").append("<x>").append(circle.getX()).append("</x>").append("\n");
    sb.append("\t").append("<y>").append(circle.getY()).append("</y>").append("\n");
    sb.append("\t").append("<radius>").append(circle.getRadius()).append("</radius>").append("\n");
    sb.append("</circle>");
    System.out.println(sb.toString());
  }

  @Override
  public void visit(Rectangle rectangle) {
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
    sb.append("<rectangle>").append("\n");
    sb.append("\t").append("<id>").append(rectangle.getId()).append("</id>").append("\n");
    sb.append("\t").append("<x>").append(rectangle.getX()).append("</x>").append("\n");
    sb.append("\t").append("<y>").append(rectangle.getY()).append("</y>").append("\n");
    sb.append("\t").append("<width>").append(rectangle.getWidth()).append("</width>").append("\n");
    sb.append("\t").append("<height>").append(rectangle.getHeight()).append("</height>").append("\n");
    sb.append("</rectangle>");
    System.out.println(sb.toString());
    
  }

}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

import java.util.HashMap;
import java.util.Map;

import com.alibaba.fastjson.JSON;

public class JSONPrintVisitor implements PrintVisitor {

  @Override
  public void visit(Circle circle) {
    Map<String, Object> map = new HashMap<>();
    map.put("circle", circle);
    String result = JSON.toJSONString(map, true);
    System.out.println(result);
  }

  @Override
  public void visit(Rectangle rectangle) {
    Map<String, Object> map = new HashMap<>();
    map.put("rectangle", rectangle);
    String result = JSON.toJSONString(map, true);
    System.out.println(result);
  }

}

写一个简单的测试程序

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class PrintVisitorMain {

  public static void main(String[] args) {
    PrintVisitor visitor = new JSONPrintVisitor();
    
    Circle circle = Circle.builder().id(1).x(1).y(1).radius(1).build();
    Rectangle rectangle = Rectangle.builder().id(2).x(3).y(3).width(2).height(2).build();

    System.out.println("使用JSON访问者:");
    visitor.visit(circle);
    visitor.visit(rectangle);
    
    System.out.println("使用XML访问者:");
    visitor = new XMLPrintVisitor();
    visitor.visit(circle);
    visitor.visit(rectangle);
  }
}

输出结果:

代码语言:javascript
复制
使用JSON访问者:
{
  "circle":{
    "id":1,
    "radius":1,
    "x":1,
    "y":1
  }
}
{
  "rectangle":{
    "height":2,
    "id":2,
    "width":2,
    "x":3,
    "y":3
  }
}
使用XML访问者:
<?xml version="1.0" encoding="utf-8"?>
<circle>
  <id>1</id>
  <x>1</x>
  <y>1</y>
  <radius>1</radius>
</circle>
<?xml version="1.0" encoding="utf-8"?>
<rectangle>
  <id>2</id>
  <x>3</x>
  <y>3</y>
  <width>2</width>
  <height>2</height>
</rectangle>

从上述输出结果中,我们可以可能看到针对一样的数据集,采用不同的访问者可以获取不一样的操作,JSON访问者看到json格式的打印;XML访问者则看到xml格式的打印。

再举一个例子

假设我们有一个文件File和文件夹Directory的列表,我们可以通过不同的过滤器访问者,可以从相同的列表中获取不同的结果集;比如输出名字以java开头的文件和文件夹。

  • 元素Element以及具体元素File和Directory
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

import lombok.Data;

@Data
public abstract class  Element {
  
  protected String name;
  
  protected long size;
  
  public Element(String name, long size) {
    super();
    this.name = name;
    this.size = size;
  }
  
  abstract void accept(Visitor visitor);
  
}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class File extends Element{

  public File(String name, long size) {
    super(name, size);
  }

  @Override
  void accept(Visitor visitor) {
    visitor.visit(this);
  }
  
}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class Directory extends Element{

  public Directory(String name, long size) {
    super(name, size);
  }

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

}
  • 访问者和具体访问者
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public interface Visitor {

  void visit(File file);
  
  void visit(Directory dir);
}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class NameAllFilterVisitor implements Visitor {

  @Override
  public void visit(File file) {
    System.out.println("File - " + file.getName());

  }

  @Override
  public void visit(Directory dir) {
    System.out.println("Directory - " + dir.getName());

  }

}
代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

public class NameStartWithFilterVisitor implements Visitor {

  private String prefix;

  public NameStartWithFilterVisitor(String prefix) {
    super();
    this.prefix = prefix;
  }

  @Override
  public void visit(File file) {
    if (file.getName().startsWith(prefix)) {
      System.out.println("File - " + file.getName());
    }

  }

  @Override
  public void visit(Directory dir) {
    if (dir.getName().startsWith(prefix)) {
      System.out.println("Directory - " + dir.getName());
    }

  }

}

测试一下:

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern.vistor;

import java.util.ArrayList;
import java.util.List;

public class Main {

  public static void main(String[] args) {
    String prefix = "Java";

    System.out.println("使用NameStartWithFilterVisitor,查看前缀为" + prefix + "信息");
    Visitor visitor = new NameStartWithFilterVisitor(prefix);

    List<Element> elements = new ArrayList<>();
    elements.add(new File("Java copybook", 1000L));
    elements.add(new File("Mysql copybook", 1000L));
    elements.add(new File("Hbase copybook", 3000L));
    elements.add(new Directory("Java", 10000L));
    elements.add(new Directory("Other", 10000L));
    for (Element ele : elements) {
      ele.accept(visitor);
    }

    System.out.println();
    System.out.println("使用NameAllFilterVisitor");
    visitor = new NameAllFilterVisitor();

    for (Element ele : elements) {
      ele.accept(visitor);
    }
  }

}

输出结果:

代码语言:javascript
复制
使用NameStartWithFilterVisitor,查看前缀为Java信息
File - Java copybook
Directory - Java

使用NameAllFilterVisitor
File - Java copybook
File - Mysql copybook
File - Hbase copybook
Directory - Java
Directory - Other

同样,在没有改变原来元素的类的前提下,我们通过定义了作用于这些元素的不一样的操作。

三. 访问者模式的优缺点

优点:

(1):访问者模式使得易于增加新的操作。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。

(2):访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。

缺点:

(1):增加新的ConcreteVisitor类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。

(2):破坏封装。访问者方法假定ConcreteElement接口的功能足够强,足以让访问者进行它们的工作。结果是,该模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。

夜已深,就写到这里吧~~~

参考

[1]. 阎宏. Java与模式.电子工业出版社

[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 访问者模式的基本介绍
  • 二. 访问者模式的示例
  • 三. 访问者模式的优缺点
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档