文档章节

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

小强斋太
 小强斋太
发布于 2016/11/09 20:06
字数 2866
阅读 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
《PHP设计模式大全》系列分享专栏

《PHP设计模式大全》已整理成PDF文档,点击可直接下载至本地查阅 https://www.webfalse.com/read/201739.html 文章 php设计模式介绍之编程惯用法第1/3页 php设计模式介绍之值对象模式第1/5页...

kaixin_code
11/06
0
0
迈向大牛的重要一步——掌握设计模式

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

一枚Sir
2015/04/10
0
0
JavaScript设计模式入坑

JavaScript设计模式入坑 介绍 设计模式编写易于维护的代码。 设计模式的开创者是一位土木工程师。Σ( ° △ °|||)︴,写代码就是盖房子。 模式 模式一种可以复用的解决方案。解决软件设计中...

小小小8021
10/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

JavaScript实现在线Markdown编辑器、转换HTML工具-toolfk程序员工具网

本文要推荐的[ToolFk]是一款程序员经常使用的线上免费测试工具箱,ToolFk 特色是专注于程序员日常的开发工具,不用安装任何软件,只要把内容贴上按一个执行按钮,就能获取到想要的内容结果。T...

toolfk
19分钟前
0
0
Source Tree 在git 密码更新后,无法拉取代码的解决办法

背景: git 密码总是需要修改。在修改代码后,拉去代码, source tree 总提示access denied. 解决方法:重新修改git 仓库对应的密码。 for mac:应用程序 -》 钥匙串 -》 搜索 source tre...

Carlyle_Lee
23分钟前
0
0
OSChina 周日乱弹 —— 懒床是对冬天最起码的尊重

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @瘟神灬念 :分享daniwellP/桃音モモ的单曲《Nyan Cat》 《Nyan Cat》- daniwellP/桃音モモ 手机党少年们想听歌,请使劲儿戳(这里) @巴拉迪...

小小编辑
今天
67
2
码云项目100,水一发

简单回顾一下: 早期构想最多的,是希望能将PHP一些类和编码分区做得更细,所以很多尝试。但不得不说,PHP的功能过于单一,是的,也许写C/C++扩展,可以解决问题,那我为什么不用C#或者Golan...

曾建凯
今天
4
0
Spring应用学习——AOP

1. AOP 1. AOP:即面向切面编程,采用横向抽取机制,取代了传统的继承体系的重复代码问题,如下图所示,性能监控、日志记录等代码围绕业务逻辑代码,而这部分代码是一个高度重复的代码,也就...

江左煤郎
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部