文档章节

设计模式(22)---->访问者模式

小强斋太
 小强斋太
发布于 2016/11/09 20:06
字数 2866
阅读 0
收藏 0
点赞 0
评论 0

访问者模式

一、引子

  对于系统中一个已经完成的类层次结构,我们已经给它提供了满足需求的接口。但是面对新增加的需求,我们应该怎么做呢?如果这是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个不错 的主意。

  但是往往我们遇到的却是:这样的需求变动也许会不停的发生;更重要的是需求的任何变动可能都要让你将整个类层次结构修改个底朝天……。这种类似的操作分布在不同的类里面,不是一个好现象,我们要对这个结构重构一下了。

  那么,访问者模式也许是你很好的选择。

二、定义

  访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。

《设计模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于java中的collection概念了。

三、结构

以下是访问者模式的组成结构:

1) 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。

2) 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。

3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。

4) 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。

5) 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。

四、例子

首先实现一个人员信息展示,然后增加一个访问者,实现工作工资统计功能。

(本例子中为了和上面的设计模式结构图一致,代码实现中去掉了下面类图中的IVisitor的两个子接口,具体访问者直接继承IVisitor因此场景类稍微改了一下,直接使用具体访问者,和设计模式之禅原例子稍微不同)

Employee基类

/**
 *在一个单位里谁都是员工,甭管你是部门经理还是小兵
 */
public abstract class Employee {
	public final static int MALE = 0; // 0代表是男性
	public final static int FEMALE = 1; // 1代表是女性
	// 甭管是谁,都有工资
	private String name;
	// 只要是员工那就有薪水
	private int salary;
	// 性别很重要
	private int sex;

	// 以下是简单的getter/setter,不多说
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getSalary() {
		return salary;
	}

	public void setSalary(int salary) {
		this.salary = salary;
	}

	public int getSex() {
		return sex;
	}

	public void setSex(int sex) {
		this.sex = sex;
	}

	// 我允许一个访问者过来访问
	public abstract void accept(IVisitor visitor);
}

普通员工类(被访问的具体类)

/**
 * 普通员工,也就是最小的小兵
 */
public class CommonEmployee extends Employee {
	
	// 工作内容,这个非常重要,以后的职业规划就是靠这个了
	private String job;

	public String getJob() {
		return job;
	}

	public void setJob(String job) {
		this.job = job;
	}

	// 我允许访问者过来访问
	@Override
	public void accept(IVisitor visitor) {
		visitor.visit(this);
	}
}

经理类(被访问的具体类)

/**
 * 经理级人物
 */
public class Manager extends Employee {
	// 这类人物的职责非常明确:业绩
	private String performance;

	public String getPerformance() {
		return performance;
	}

	public void setPerformance(String performance) {
		this.performance = performance;
	}

	// 部门经理允许访问者访问
	@Override
	public void accept(IVisitor visitor) {
		visitor.visit(this);
	}
}

IVisitor接口

public interface IVisitor {
	// 首先定义我可以访问普通员工
	public void visit(CommonEmployee commonEmployee);

	// 其次定义,我还可以访问部门经理
	public void visit(Manager manager);
}

ShowVisitor

public class ShowVisitor implements IVvisitor{
	private String info = "";

	// 打印出报表
	public void report() {
		System.out.println(this.info);
	}

	// 访问普通员工,组装信息
	public void visit(CommonEmployee commonEmployee) {
		this.info = this.info + this.getBasicInfo(commonEmployee) + "工作:"
				+ commonEmployee.getJob() + "\t\n";
	}

	// 访问经理,然后组装信息
	public void visit(Manager manager) {
		this.info = this.info + this.getBasicInfo(manager) + "业绩:"
				+ manager.getPerformance() + "\t\n";
	}

	// 组装出基本信息
	private String getBasicInfo(Employee employee) {
		String info = "姓名:" + employee.getName() + "\t";
		info = info + "性别:"
				+ (employee.getSex() == Employee.FEMALE ? "女" : "男") + "\t";
		info = info + "薪水:" + employee.getSalary() + "\t";
		return info;
	}
}

场景类

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

public class Client {
	public static void main(String[] args) {
		// 展示报表访问者
		ShowVisitor showVisitor = new ShowVisitor();
		for (Employee emp : mockEmployee()) {
			emp.accept(showVisitor); // 接受展示报表访问者
		}
		// 展示报表
		showVisitor.report();
	}

	// 模拟出公司的人员情况,我们可以想象这个数据室通过持久层传递过来的
	public static List<Employee> mockEmployee() {
		List<Employee> empList = new ArrayList<Employee>();
		// 产生张三这个员工
		CommonEmployee zhangSan = new CommonEmployee();
		zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
		zhangSan.setName("张三");
		zhangSan.setSalary(1800);
		zhangSan.setSex(Employee.MALE);
		empList.add(zhangSan);
		// 产生李四这个员工
		CommonEmployee liSi = new CommonEmployee();
		liSi.setJob("页面美工,审美素质太不流行了!");
		liSi.setName("李四");
		liSi.setSalary(1900);
		liSi.setSex(Employee.FEMALE);
		empList.add(liSi);
		// 再产生一个经理
		Manager wangWu = new Manager();
		wangWu.setName("王五");
		wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
		wangWu.setSalary(18750);
		wangWu.setSex(Employee.MALE);
		empList.add(wangWu);
		return empList;

	}

}

运行结果

姓名:张三 性别:男 薪水:1800 工作:编写Java程序,绝对的蓝领、苦工加搬运工 
姓名:李四 性别:女 薪水:1900 工作:页面美工,审美素质太不流行了! 
姓名:王五 性别:男 薪水:18750 业绩:基本上是负值,但是我会拍马屁呀 

多个访问者(增加一个访问者,实现工资统计)

/**
 * 汇总表,该访问者起汇总作用,把容器中的数据一个一个遍历,然后汇总
 */
public class TotalVisitor implements IVisitor {
	// 部门经理的工资系数是5
	private final static int MANAGER_COEFFICIENT = 5;
	// 员工的工资系数是2
	private final static int COMMONEMPLOYEE_COEFFICIENT = 2;
	// 普通员工的工资总和
	private int commonTotalSalary = 0;
	// 部门经理的工资总和
	private int managerTotalSalary = 0;

	public void totalSalary() {
		System.out.println("本公司的月工资总额是"
				+ (this.commonTotalSalary + this.managerTotalSalary));
	}

	// 访问普通员工,计算工资总额
	public void visit(CommonEmployee commonEmployee) {
		this.commonTotalSalary = this.commonTotalSalary
				+ commonEmployee.getSalary() * COMMONEMPLOYEE_COEFFICIENT;
	}

	// 访问部门经理,计算工资总额
	public void visit(Manager manager) {
		this.managerTotalSalary = this.managerTotalSalary + manager.getSalary()
				* MANAGER_COEFFICIENT;
	}
}

Client 场景类

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

public class Client {
	public static void main(String[] args) {
		
		// 汇总报表的访问者
		TotalVisitor totalVisitor = new TotalVisitor();
		for (Employee emp : mockEmployee()) {
			emp.accept(totalVisitor);// 接受汇总表访问者
		}
		// 汇总报表
		totalVisitor.totalSalary();
	}

	// 模拟出公司的人员情况,我们可以想象这个数据室通过持久层传递过来的
	public static List<Employee> mockEmployee() {
		List<Employee> empList = new ArrayList<Employee>();
		// 产生张三这个员工
		CommonEmployee zhangSan = new CommonEmployee();
		zhangSan.setJob("编写Java程序,绝对的蓝领、苦工加搬运工");
		zhangSan.setName("张三");
		zhangSan.setSalary(1800);
		zhangSan.setSex(Employee.MALE);
		empList.add(zhangSan);
		// 产生李四这个员工
		CommonEmployee liSi = new CommonEmployee();
		liSi.setJob("页面美工,审美素质太不流行了!");
		liSi.setName("李四");
		liSi.setSalary(1900);
		liSi.setSex(Employee.FEMALE);
		empList.add(liSi);
		// 再产生一个经理
		Manager wangWu = new Manager();
		wangWu.setName("王五");
		wangWu.setPerformance("基本上是负值,但是我会拍马屁呀");
		wangWu.setSalary(18750);
		wangWu.setSex(Employee.MALE);
		empList.add(wangWu);
		return empList;

	}

}

 运行结果

本公司的月工资总额是101150

五、访问者模式与双分派

double dispatch(双分派)则在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时型别(Run time type),还要根据参数的运行时型别(Run time type)。

double dispatch(双分派)是multi-dispatch(多分派)的特例,由于Visitor模式涉及的是double dispatch(双分派)。首先在客户程序中将具体访问者模式作为参数传递给具体元素角色,这便完成了一次分派。进入具体元素角色后,具体元素角 色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行。这便完成了第二次分派。

http://blog.csdn.net/ncepustrong/article/details/9070177

http://blog.chinaunix.net/uid-20665047-id-3269091.html

http://www.iteye.com/topic/1130764

五、优缺点

5.1、访问者模式的优点

·符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。

·扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。

5.2、缺点

   访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。

六、 访问者模式的适用场景

访问者模式的目的是要把处理从数据结构中分离出来,如果系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式是个不错的选择,因为访问者模式使的算法操作的增加变得容易。相反,如果系统的数据结构不稳定,易于变化,则此系统就不适合使用访问者模式了。

  • 假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
  • 假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。

参考

例子  设计模式之

双分派

http://blog.chinaunix.net/uid-20665047-id-3269091.html

http://www.uml.org.cn/sjms/201208093.asp

http://www.iteye.com/topic/1130764

 

 

本文转载自:http://www.cnblogs.com/xqzt/archive/2013/06/09/5637046.html

共有 人打赏支持
小强斋太
粉丝 0
博文 181
码字总数 0
作品 0
广州
设计模式 2014-12-19

book: 阎宏《JAVA与模式》 架构设计栏目 http://blog.csdn.net/enterprise/column.html 概要: http://bbs.csdn.net/forums/Embeddeddriver 23种设计模式分别是: 1.单例模式 2.工厂方法模式...

jayronwang
2014/12/19
0
0
(目录)设计模式(可复用面向对象软件的基础)

本系列“设计模式”博客使用Golang语言实现算法。所谓算法是指解决一个问题的步骤,个人觉得不在于语言。小弟只是最近学习Golang,所以顺带熟练一下语法知识,别无它意。 本系列博客主要介绍...

chapin
2015/01/13
0
0
java设计模式-- 单例模式

在很久之前,也就是在大二暑假的时候,那时候看马士兵的视频教程中有提到很多的设计模式。 java的设计模式大致可以分为3大类,23种设计模式。 其中,创建型模式有5种:单例模式、建造者模式、...

爱学习的逃课君
2014/11/27
0
0
迈向大牛的重要一步——掌握设计模式

IT职场的小菜经常有这样的疑问: 为什么一个相似的功能,大牛一会儿就搞定,然后悠闲地品着下午茶逛淘宝;而自己加班加点搞到天亮还做不完。 为什么用户提出需求变更后,大牛只需潇洒地敲敲键...

一枚Sir
2015/04/10
0
0
设计模式22——Visitor设计模式

Visitor访问者设计模式是在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。 Visitor访问者设计模式的角色: (1) 访问者角色(Visitor):声明一个访问接...

小米米儿小
2014/01/27
0
0
设计模式Java Design Pattern-工厂方法模式FactoryMethod

我的博客 一、 设计模式的分类 大体可以分为三类: 创建型模式(5个) 单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式 结构性模式(7个) 适配器模式、装饰器模式、代理模式、...

勇敢写信
03/22
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
06/24
0
0
设计模式笔录(二),设计模式有哪些

本人出道5年,学习、编程、再学习、再编程一路走过,只是在笔和纸留下些脚印,实感惭愧。现开始把自己学习到的心得,实践中的体会,一一贴在互联网上,大家互相学习、探讨,寻找一些技术朋友...

方旭
2011/03/31
0
0
你需要了解的23种JavaScript设计模式

为什么要学习设计模式? 在许多访谈中,你可能会遇到很多面向对象编程中的接口,抽象类,代理和以及其他与设计模式相关的问题。 一旦了解了设计模式,它会让你轻松应对任何访谈,并可以在你的...

java高级架构牛人
06/02
0
0
系统架构技能之设计模式-单件模式

一、开篇 其实我本来不是打算把系统架构中的一些设计模式单独抽出来讲解的,因为很多的好朋友也比较关注这方面的内容,所以我想通过我理解及平时项目中应用到的一 些常见的设计模式,拿出来给...

wbf961127
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Spark Streaming + Kafka Integration Guide

The Spark Streaming integration for Kafka 0.10 is similar in design to the 0.8 Direct Stream approach. It provides simple parallelism, 1:1 correspondence between Kafka partition......

刺猬一号
10分钟前
0
0
数据结构与算法2

一个数组的例子,实现查找,显示和删除的功能。 在这个数组中存储的数据类型是long型,使用long型为的是表明这是数据,而int型被用来表示下标。通常数据结构存储的数据项包含有好几个字段,所...

沉迷于编程的小菜菜
20分钟前
0
0
Python3 基于 requests 批量下载图片

Python3 基于 requests 批量下载图片 import requestsheaders = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8','Accept-Encod......

leeyi
21分钟前
0
0
java获取当前时间所在一周的周一和周日日期

/** * 当前时间所在一周的周一和周日时间 * @param time 当前时间 * @return */ public static Map getWeekDate(String time) { Map map = new HashedMap(); SimpleDateFormat sdf = new Si......

小弱鸡
53分钟前
0
0
Redis数据的导出和导入(dump和load方式)

网上有些文章已经不再适用,本人也是踩了些坑,在此记录下。 迁移redis数据一般有如下3种方式: 第三方工具redis-dump,redis-load aof机制,需要开启aof功能 rdb存储机制 这里介绍第一种方式...

iplusx
58分钟前
2
0
ElasticSearch 高亮显示大文档搜索结果

2016年12月,我们开始研究Ambar——一个文档搜索系统。Ambar使用ElasticSearch作为核心搜索引擎。 在Ambar开发的过程中,我们处理了很多与ES相关的问题,我们想分享我们得到的宝贵经验。让我...

九州暮云
今天
1
0
Python 使用 pywifi 模块 破解wifi密码

git https://github.com/awkman/pywifi 常见常量 from pywifi import const# Define interface status.IFACE_DISCONNECTED = 0IFACE_SCANNING = 1IFACE_INACTIVE = 2IFACE_CONNEC......

阿豪boy
今天
2
0
phpstorm使用Iedis

phpstorm的redis插件Iedis是真好用 看了网上挺多的文章,但是由于我系统还是ubuntu,就有点尴尬了,现在破解之后,留个笔记,即使自己之后有需要也可以很快翻阅 先下载资源 资源下载 zip压缩...

贤郎--均灵
今天
0
0
第三章 spring-bean之FactoryBeanRegistrySupport(4)

前言 从FactoryBeanRegistrySupport类的名字可以看出FactoryBeanRegistrySupport负责FactoryBean的注册与支持。如果想知道FactoryBean相关的资料,请阅读spring-bean中关于FactoryBean的解读...

鸟菜啊
今天
0
0
CentOS “Destination Host Unreachable”问题解决办法

挑战极速安装CentOS时遇到局域网主机不能通信的情况: [root@zjd network-scripts]# ping 8.8.8.8PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.64 bytes from 8.8.8.8: icmp_seq=1 ttl=......

wffger
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部