简说设计模式——访问者模式

一、什么是访问者模式

  访问者模式是一个相对比较简单,但结构又稍显复杂的模式,它讲的是表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。例如,你在朋友家做客,你是访问者,朋友接收你的访问,你通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。

  访问者模式(Visitor),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。UML结构图如下:

  其中,Visitor是抽象访问者,为该对象结构中ConcreteElement的每一个类声明一个Visit操作;ConcreteVisitor是具体访问者,实现每个由visitor声明的操作,是每个操作实现算法的一部分,而该算法片段是对应于结构中对象的类;ObjectStructure为能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素;Element定义了一个Accept操作,它以一个访问者为参数;ConcreteElement为具体元素,实现Accept操作。

  1. 抽象访问者

  此处可为抽象类或接口,用于声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。

1 public abstract class Visitor {
2
3     public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);
4
5     public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
6
7 }

  2. 具体访问者

  影响访问者访问到一个类后该干什么、怎么干。这里以ConcreteVisitor1为例,ConcreteVisitor2就不再赘述了。

1 public class ConcreteVisitor1 extends Visitor {
 2
 3     @Override
 4     public void visitConcreteElementA(ConcreteElementA concreteElementA) {
 5         System.out.println(concreteElementA.getClass().getName() + " 被 " + this.getClass().getName() + " 访问");
 6     }
 7
 8     @Override
 9     public void visitConcreteElementB(ConcreteElementB concreteElementB) {
10         System.out.println(concreteElementB.getClass().getName() + " 被 " + this.getClass().getName() + " 访问");
11     }
12
13 }

  3. 抽象元素

  此处为接口后抽象类,用于声明接受哪一类访问者访问,程序上是通过accpet方法中的参数来定义的。

  抽象元素有两类方法,一是本身的业务逻辑,也就是元素作为一个业务处理单元必须完成的职责;另外一个是允许哪一个访问者来访问。这里只声明的第二类即accept方法。

1 public abstract class Element {
2
3     public abstract void accept(Visitor visitor);
4
5 }

  4. 具体元素

  实现accept方法,通常是visitor.visit(this)。这里以ConcreteElementA为例,ConcreteElementB就不再赘述了。

1 public class ConcreteElementA extends Element {
 2
 3     @Override
 4     public void accept(Visitor visitor) {
 5         visitor.visitConcreteElementA(this);
 6     }
 7
 8     //其它方法
 9     public void operationA() {
10
11     }
12
13 }

  5. 结构对象

  元素生产者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。

1 public class ObjectStructure {
 2
 3     private List<Element> elements = new LinkedList<>();
 4
 5     public void attach(Element element) {
 6         elements.add(element);
 7     }
 8
 9     public void detach(Element element) {
10         elements.remove(element);
11     }
12
13     public void accept(Visitor visitor) {
14         for (Element element : elements) {
15             element.accept(visitor);
16         }
17     }
18
19 }

  6. Client客户端

  我们通过以下场景模拟一下访问者模式。

1 public class Client {
 2
 3     public static void main(String[] args) {
 4         ObjectStructure objectStructure = new ObjectStructure();
 5
 6         objectStructure.attach(new ConcreteElementA());
 7         objectStructure.attach(new ConcreteElementB());
 8
 9         ConcreteVisitor1 visitor1 = new ConcreteVisitor1();
10         ConcreteVisitor2 visitor2 = new ConcreteVisitor2();
11
12         objectStructure.accept(visitor1);
13         objectStructure.accept(visitor2);
14     }
15
16 }

  运行结果如下:

  

二、访问者模式的应用

  1. 何时使用

  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类时

  2. 方法

  • 在被访问的类里面添加一个对外提供接待访问者的接口

  3. 优点

  • 符合单一职责原则
  • 优秀的扩展性
  • 灵活性非常高

  4. 缺点

  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的
  • 具体元素变更比较困难
  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素

  5. 使用场景

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖与其具体类的操作,也就是用迭代器模式已经不能胜任的情景
  • 需要对一个对结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象

  6. 目的

  • 把处理从数据结构分离出来

  7. 应用实例

  • 人类只分为男人和女人,这个性别分类是稳定的,可以在状态类中,增加“男人反应”和“女人反应”两个方法,方法个数是稳定的,不会很容易发生变化
  • 你在朋友家做客,你是访问者,朋友接受你的访问,你通过朋友的描述,然后对朋友的描述做出一个判断

  8. 注意事项

  • 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器
  • 访问者模式适用于数据结构相对稳定的系统

三、访问者模式的实现

  下面就以上述应用实例中的人类分为男人和女人这个例子来实现访问者模式。UML图如下:

  1. Action

  抽象的状态类,主要声明以下两个方法。

  这里的关键在于人只分男人和女人,这个性别的分类是稳定的,所以可以在状态类中,增加“男人反应”和“女人反应”两个方法,方法个数是稳定的,不会容易发生变化。

1 public abstract class Action {
2
3     //得到男人的结论或反应
4     public abstract void getManConclusion(Man man);
5
6     //得到女人的结论或反应
7     public abstract void getWomanConclusion(Woman woman);
8
9 }

  2. Person

  人的抽象类。只有一个“接受”的抽象方法,它是用来获得“状态”对象的。

1 public abstract class Person {
2
3     //接受
4     public abstract void accept(Action action);
5
6 }

  3. Action类的具体实现类

  这里以成功类(Success)为例,失败类(Fail)同理。

1 public class Success extends Action {
 2
 3     @Override
 4     public void getManConclusion(Man man) {
 5         System.out.println("男人成功...");
 6     }
 7
 8     @Override
 9     public void getWomanConclusion(Woman woman) {
10         System.out.println("女人成功...");
11     }
12
13 }

  4. Person类的具体实现类

  这里以男人类(Man)为例,女人类(Woman)同理。

  这里用到了双分派,即首先在客户程序中将具体状态作为参数传递给Man类完成了一次分派,然后Man类调用作为参数的“具体方法”中的方法getManConclusion(),同时将自己(this)作为参数传递进去,这便完成了第二次分派。accept方法就是一个双分派操作,它得到执行的操作不仅决定于Action类的具体状态,还决定于它访问的Person的类别。

1 public class Man extends Person {
2
3     @Override
4     public void accept(Action action) {
5         action.getManConclusion(this);
6     }
7
8 }

  5. 结构对象

1 public class ObjectStructure {
 2
 3     private List<Person> elements = new LinkedList<>();
 4
 5     //增加
 6     public void attach(Person person) {
 7         elements.add(person);
 8     }
 9
10     //移除
11     public void detach(Person person) {
12         elements.remove(person);
13     }
14
15     //查看显示
16     public void display(Action action) {
17         for (Person person : elements) {
18             person.accept(action);
19         }
20     }
21
22 }

  6. Client客户端

1 public class Client {
 2
 3     public static void main(String[] args) {
 4         ObjectStructure objectStructure = new ObjectStructure();
 5
 6         objectStructure.attach(new Man());
 7         objectStructure.attach(new Woman());
 8
 9         //成功
10         Success success = new Success();
11         objectStructure.display(success);
12
13         //失败
14         Failing failing = new Failing();
15         objectStructure.display(failing);
16     }
17
18 }

  运行结果如下:

  

四、双分派

  上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。

  以上述实例为例,假设我们要添加一个Marray的状态类来考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用来查看,不需要改动任何其他类的代码。

  而单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的,在Java中有静态绑定和动态绑定之说,它的实现是依据重载和重写实现的。值得一提的是,Java是一个支持双分派的单分派语言。

  源码地址:https://gitee.com/adamjiangwh/GoF

(0)

相关推荐

  • 访问者模式

    假设有男人和女人两种元素,要分别打印出他们在不同状态时的不同表现. 用OO的思想把表现(行为)提取出来作为一个抽象方法,代码如下: 用if-else对状态进行判断  Person接口 public i ...

  • 设计模式(十六)——访问者模式

    设计模式(十六)——访问者模式

  • 软件设计模式修炼 -- 访问者模式

    访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素具有不同的类型,且不同的访问者可以对其进行不同的访问操作 模式动机 对于系统中某些对象,它们存储在同 ...

  • 诚之和:设计模式之什么是访问者模式

    本篇内容介绍了"设计模式之什么是访问者模式"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学 ...

  • C#设计模式学习笔记:(21)访问者模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8135083.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第九个模式--访 ...

  • PHP设计模式—访问者模式

    定义: 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 结构: Visitor:抽象访问者,为该对象结构中Co ...

  • 简说设计模式——迭代器模式

    一.什么是迭代器模式 迭代器这个词在Java中出现过,即Java中使用Iterator迭代器对集合进行遍历,但迭代器模式算是一个没落的模式,基本上没人会单独写一个迭代器,除非是产品性质的开发. 迭代器 ...

  • 简说设计模式——解释器模式

    一.什么是解释器模式 解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树.诸如此类 ...

  • 简说设计模式——状态模式

    一.什么是状态模式 状态这个词汇我们并不陌生,在日常生活中,不同时间就有不同的状态,早上起来精神饱满,中文想睡觉,下午又渐渐恢复,晚上可能精神更旺也可能耗费体力只想睡觉,这一天中就对应着不同的状态.或 ...

  • 简说设计模式——命令模式

    一.什么是命令模式 在说命令模式前我们先来说一个小例子.很多人都有吃夜市的经历,对于那些推小车的摊位,通常只有老板一个人,既负责制作也负责收钱,我要两串烤串多放辣,旁边的人要了三串烤面筋不要辣,过了一 ...

  • PHP设计模式之访问者模式

    PHP设计模式之访问者模式 访问者,就像我们去别人家访问,或者别人来我们家看望我们一样.我们每个人都像是一个实体,而来访的人都会一一的和我们打招呼.毕竟,我们中华民族是非常讲究礼数和好客的民族.访问者 ...

  • [PHP小课堂]PHP设计模式之访问者模式

    [PHP小课堂]PHP设计模式之访问者模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料