Visitor 模式心得
最近读到Visitor模式,还是一知半解的。偶然翻到Uncle Bob对该模式的推导过程,有所心得,和大家分享一下。 Uncle Bob 的链接是: http://butunclebob.com/ArticleS.UncleBob.VisitorVersusInstanceOf。个人觉得该模式用来操作复杂对象集合,特别适用于报表生成。因为报表的来源相对稳定(复杂数据集合),但是表现形式却是千变万化。言归正传,我将该博客的内容按照自己的理解分享出来,如果有什么不对的地方,请指正。
首先有一个如下简单的场景,Bob先生的公司提供计算机方面的培训服务,对社会人士开设二门课程,一门是OOD, 一门是Java,java课程需要上机(结对编程,所以二个人用一台机器),所以需要根据报名的时间计算具体的机器数目:

public abstract class Course {
protected GregorianCalendar startDate;
protected int students;
public Course(GregorianCalendar startDate, int students) {
this.startDate = (GregorianCalendar) startDate.clone();
this.students = students;
}
abstract public int getComputersInUse(GregorianCalendar date);
}
public class AOODCourse extends Course {
public AOODCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
}
public int getComputersInUse(GregorianCalendar date) {
return 0;
}
}
public class JavaCourse extends Course {
private GregorianCalendar endDate;
public JavaCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
public int getComputersInUse(GregorianCalendar date) {
int resources = 0;
if (!date.before(startDate) && !date.after(endDate)) {
resources = Math.round(students/2);
}
return resources;
}
}
代码实现了,B先生很happy。不久客户说需要生一份报表,B先生想了5分钟,洋洋洒洒写下如下代码:

public abstract class Course {
protected GregorianCalendar startDate;
protected int students;
protected SimpleDateFormat dateFormat;
public Course(GregorianCalendar startDate, int students) {
this.startDate = (GregorianCalendar) startDate.clone();
this.students = students;
this.dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public String generateComputerReportLine() {
StringBuffer line = new StringBuffer();
line.append(dateFormat.format(startDate.getTime())).append(" ")
.append(courseName()).append(" ")
.append(computersInUse()).append(" Computers\n");
return line.toString();
}
public GregorianCalendar getStartDate() {
return startDate;
}
protected abstract String courseName();
protected abstract String computersInUse();
}
public class AOODCourse extends Course {
public AOODCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
}
@Override
protected String courseName() {
return "AOOD";
}
@Override
protected String computersInUse() {
return "0";
}
}
public class JavaCourse extends Course {
private GregorianCalendar endDate;
public JavaCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
@Override
protected String courseName() {
return "Java";
}
@Override
protected String computersInUse() {
return String.valueOf(Math.round(students/2));
}
}
public class CourseResourceTracker<T extends Course> {
Set<T> courses = new HashSet<>();
public String generateReport() {
StringBuilder reports = new StringBuilder();
for (Iterator i = courses.iterator(); i.hasNext();) {
T course = (T) i.next();
reports.append(course.generateComputerReportLine()) ;
}
return reports.toString();
}
public void add(T course) {
if (course == null) return;
courses.add(course);
}
public void remove(T course) {
if (course == null) return;
courses.remove(course);
}
public static void main(String[] param) {
CourseResourceTracker tracker = new CourseResourceTracker();
tracker.add(new AOODCourse(new GregorianCalendar(2015, 3, 20), 10));
tracker.add(new JavaCourse(new GregorianCalendar(2015, 3, 20), 10));
System.out.println("Report is : " + tracker.generateReport());
}
}
报表很简单, 打印结果如下, 格式为: 时间 + 名称 + 数量
Report is : 04/20/2015 AOOD 0 Computers
04/20/2015 Java 5 Computers
B洋洋得意,客户又来了新的需求,前面的报表太简单了,需要针对不同的课程打印不同的内容:

public abstract class Course {
protected GregorianCalendar startDate;
protected int students;
protected SimpleDateFormat dateFormat;
public Course(GregorianCalendar startDate, int students) {
this.startDate = (GregorianCalendar) startDate.clone();
this.students = students;
this.dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public GregorianCalendar getStartDate() {
return startDate;
}
abstract public int getComputersInUse(GregorianCalendar date);
}
public class AOODCourse extends Course {
public AOODCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
}
public int getComputersInUse(GregorianCalendar date) {
return 0;
}
}
public class JavaCourse extends Course {
private GregorianCalendar endDate;
public JavaCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
public int getComputersInUse(GregorianCalendar date) {
int resources = 0;
if (!date.before(startDate) && !date.after(endDate)) {
resources = Math.round(students/2);
}
return resources;
}
public int getTotalComputers() {
return students/2;
}
}
public class CourseComputerReport {
protected SimpleDateFormat dateFormat;
public CourseComputerReport() {
dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public String generateComputerReport(List courses) {
if (courses == null || courses.size() == 0) return null;
StringBuffer report = new StringBuffer();
for (Iterator i = courses.iterator(); i.hasNext();) {
Course course = (Course) i.next();
report.append(dateFormat.format(course.getStartDate().getTime())).append(" ");
if (course instanceof AOODCourse) {
report.append("AOOD 0 Computers\n");
} else if (course instanceof JavaCourse) {
report.append("Java ");
JavaCourse jc = (JavaCourse)course;
report.append(String.valueOf(jc.getTotalComputers())).append(" Computers\n");
}
}
return report.toString();
}
public static void main(String[] param) {
CourseComputerReport report = new CourseComputerReport();
List<Course> courses = new ArrayList<>();
courses.add(new AOODCourse(new GregorianCalendar(2015, 3, 20), 10));
courses.add(new JavaCourse(new GregorianCalendar(2015, 3, 20), 10));
System.out.println("Report is : " + report.generateComputerReport(courses));
}
}
从上面的UML 类图可以看出,CourseComputerReport 因为对每门课程的format不一致,需要遍历的时候针对不同的课程设置不同的格式从而导致CourseReport和Course之前出现强耦合, 可以通过重构方法 generateComputerReport 使之更加合理:

public abstract class CourseReport {
protected SimpleDateFormat dateFormat;
public CourseReport() {
dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public String generateComputerReport(List courses) {
StringBuffer report = new StringBuffer();
for (Iterator i = courses.iterator(); i.hasNext();) {
Course course = (Course) i.next();
report.append(dateFormat.format(course.getStartDate().getTime())).append(" ");
if (course instanceof AOODCourse) {
appendAOODLine((AOODCourse)course, report);
} else if (course instanceof JavaCourse) {
appendJavaLine((JavaCourse) course, report);
}
}
return report.toString();
}
protected abstract void appendJavaLine(JavaCourse course, StringBuffer report);
protected abstract void appendAOODLine(AOODCourse course, StringBuffer report);
}
public class CourseComputerReport extends CourseReport{
protected void appendJavaLine(JavaCourse course, StringBuffer report) {
report.append("Java ");
report.append(String.valueOf(course.getTotalComputers())).append(" Computers\n");
}
protected void appendAOODLine(AOODCourse course, StringBuffer report) {
report.append("AOOD 0 Computers\n");
}
public static void main(String[] param) {
CourseComputerReport report = new CourseComputerReport();
List<Course> courses = new ArrayList<>();
courses.add(new AOODCourse(new GregorianCalendar(2015, 3, 20), 10));
courses.add(new JavaCourse(new GregorianCalendar(2015, 3, 20), 10));
System.out.println("Report is : " + report.generateComputerReport(courses));
}
}
老鸟L看了B的实现后,提出了自己的看法,为什么 CourseReport 需要依赖具体的Course, 能否将Course告诉reporter 而不是Reporter来判断具体的Course。 并将Visitor的经典类图随手抛给小B:

小B恍然大悟,修改代码如下:

public abstract class Course {
protected GregorianCalendar startDate;
protected int students;
protected SimpleDateFormat dateFormat;
public Course(GregorianCalendar startDate, int students) {
this.startDate = (GregorianCalendar) startDate.clone();
this.students = students;
this.dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public GregorianCalendar getStartDate() {
return startDate;
}
abstract void accept(CourseVisitor courseVisitor);
public class AOODCourse extends Course {
public AOODCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
}
@Override
void accept(CourseVisitor courseVisitor) {
courseVisitor.visit(this);
}
}
public class JavaCourse extends Course {
private GregorianCalendar endDate;
public JavaCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
@Override
void accept(CourseVisitor courseVisitor) {
courseVisitor.visit(this);
}
public int getTotalComputers() {
return students/2;
}
}
public interface CourseVisitor {
void visit(AOODCourse aoodCourse);
void visit(JavaCourse javaCourse);
}
public class ReportCourseVisitor implements CourseVisitor {
protected StringBuffer report;
public ReportCourseVisitor(StringBuffer report) {
this.report = report;
}
@Override
public void visit(AOODCourse aoodCourse) {
report.append("AOOD 0 Computers\n");
}
@Override
public void visit(JavaCourse javaCourse) {
report.append("Java ");
report.append(String.valueOf(javaCourse.getTotalComputers())).append(" Computers\n");
}
}
public abstract class CourseReport {
protected SimpleDateFormat dateFormat;
public CourseReport() {
dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public String generateComputerReport(List courses) {
StringBuffer report = new StringBuffer();
CourseVisitor v = makeReportVisitor(report);
for (Iterator i = courses.iterator(); i.hasNext();) {
Course course = (Course) i.next();
report.append(dateFormat.format(course.getStartDate().getTime())).append(" ");
course.accept(v);
}
return report.toString();
}
protected abstract CourseVisitor makeReportVisitor(StringBuffer report);
}
public class CourseComputerReport extends CourseReport{
public static void main(String[] param) {
CourseComputerReport report = new CourseComputerReport();
List<Course> courses = new ArrayList<>();
courses.add(new AOODCourse(new GregorianCalendar(2015, 3, 20), 10));
courses.add(new JavaCourse(new GregorianCalendar(2015, 3, 20), 10));
System.out.println("Report is : " + report.generateComputerReport(courses));
}
@Override
protected CourseVisitor makeReportVisitor(StringBuffer report) {
return new ReportCourseVisitor(report);
}
}
老鸟L看了以后表示满意,同时指出如果添加新的Course,由于所有的course都依赖CourseVisitor,而courseVisitor需要添加新的接口,所有的course需要重新打包和编译。是否可以新建一个空接口CourseVisitor 使Course依赖这个接口从而达到隔离变化的目的。需要修改的是需要将courseVisitor Cast到具体的实现类,主要的代码实现是:
@Override
void accept(CourseVisitor courseVisitor) {
if (courseVisitor instanceof AOODCourseVisitor) {
((AOODCourseVisitor) courseVisitor).visit(this);
}
}
小B撸撸袖子,大笔一挥,修改代码如下:

public abstract class Course {
protected GregorianCalendar startDate;
protected int students;
protected SimpleDateFormat dateFormat;
public Course(GregorianCalendar startDate, int students) {
this.startDate = (GregorianCalendar) startDate.clone();
this.students = students;
this.dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public GregorianCalendar getStartDate() {
return startDate;
}
abstract void accept(CourseVisitor courseVisitor);
}
public class AOODCourse extends Course {
public AOODCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
}
@Override
void accept(CourseVisitor courseVisitor) {
if (courseVisitor instanceof AOODCourseVisitor) {
((AOODCourseVisitor) courseVisitor).visit(this);
}
}
}
public class JavaCourse extends Course {
private GregorianCalendar endDate;
public JavaCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
@Override
void accept(CourseVisitor courseVisitor) {
if (courseVisitor instanceof JavaCourseVisitor) {
((JavaCourseVisitor) courseVisitor).visit(this);
}
}
public int getTotalComputers() {
return students/2;
}
}
public class AndroidCourse extends Course {
private GregorianCalendar endDate;
public AndroidCourse(GregorianCalendar startDate, int students) {
super(startDate, students);
endDate = (GregorianCalendar) startDate.clone();
endDate.add(Calendar.DAY_OF_WEEK, 4);
}
@Override
void accept(CourseVisitor courseVisitor) {
if (courseVisitor instanceof AndroidCourseVisitor) {
((AndroidCourseVisitor) courseVisitor).visit(this);
}
}
public int getTotalComputers() {
return students/2;
}
}
public interface CourseVisitor {
}
public interface AndroidCourseVisitor {
void visit(AndroidCourse androidCourse);
}
public interface AOODCourseVisitor {
void visit(AOODCourse aoodCourse);
}
public interface JavaCourseVisitor {
void visit(JavaCourse javaCourse);
}
public abstract class CourseReport {
protected SimpleDateFormat dateFormat;
public CourseReport() {
dateFormat = new SimpleDateFormat("MM/dd/yyyy");
}
public String generateComputerReport(List courses) {
StringBuffer report = new StringBuffer();
CourseVisitor v = makeCourseVisitor(report);
for (Iterator i = courses.iterator(); i.hasNext();) {
Course course = (Course) i.next();
report.append(dateFormat.format(course.getStartDate().getTime())).append(" ");
course.accept(v);
}
return report.toString();
}
protected abstract CourseVisitor makeCourseVisitor(StringBuffer report);
}
public class CourseComputerReport extends CourseReport {
public static void main(String[] param) {
CourseComputerReport report = new CourseComputerReport();
List<Course> courses = new ArrayList<>();
courses.add(new AOODCourse(new GregorianCalendar(2015, 3, 20), 10));
courses.add(new JavaCourse(new GregorianCalendar(2015, 3, 20), 10));
courses.add(new AndroidCourse(new GregorianCalendar(2015, 3, 20), 10));
System.out.println("Report is : " + report.generateComputerReport(courses));
}
@Override
protected CourseVisitor makeCourseVisitor(StringBuffer report) {
return new ReportCourseVisitor(report);
}
}
顺便说一下,上面使用的模式名称是Acyclic visitor, 是为了解决添加新的item 导致所有的item需要重新编译和打包的问题。
Visitor 模式心得的更多相关文章
- 完成C++不能做到的事 - Visitor模式
拿着刚磨好的热咖啡,我坐在了显示器前.“美好的一天又开始了”,我想. 昨晚做完了一个非常困难的任务并送给美国同事Review,因此今天只需要根据他们提出的意见适当修改代码并提交,一周的任务就完成了.剩 ...
- Visitor模式,Decorator模式,Extension Object模式
Modem结构 Visitor模式 对于被访问(Modem)层次结构中的每一个派生类,访问者(Visitor)层次中都有一个对应的方法. 从派生类到方法的90度旋转. 新增类似的Windows配置函数 ...
- 设计模式之visitor模式,人人能懂的有趣实例
设计模式,现在在网上随便搜都一大堆,为什么我还要写"设计模式"的章节呢? 两个原因: 1.本人觉得这是一个有趣的设计模式使用实例,所以记下来: 2.看着设计模式很牛逼,却不知道怎么 ...
- 设计模式:基于线程池的并发Visitor模式
1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...
- Java 的双重分发与 Visitor 模式
双重分发(Double Dispatch) 什么是双重分发? 谈起面向对象的程序设计时,常说起的面向对象的「多态」,其中关于多态,经常有一个说法是「父类引用指向子类对象」. 这种父类的引用指向子类对象 ...
- 【转载】完成C++不能做到的事 - Visitor模式
原文: 完成C++不能做到的事 - Visitor模式 拿着刚磨好的热咖啡,我坐在了显示器前.“美好的一天又开始了”,我想. 昨晚做完了一个非常困难的任务并送给美国同事Review,因此今天只需要根据 ...
- 设计模式之——visitor模式
visitor模式,又叫访问者模式,把结构和数据分开,编写一个访问者,去访问数据结构中的元素,然后把对各元素的处理全部交给访问者类.这样,当需要增加新的处理时候,只需要编写新的 访问者类,让数据结构可 ...
- Visitor模式(访问者设计模式)
Visitor ? 在Visitor模式中,数据结构与处理被分离开来.我们编写一个表示"访问者"的类来访问数据结构中的元素, 并把对各元素的处理交给访问者类.这样,当需要增加新的处 ...
- Behavioral模式之Visitor模式
1.意图 表示一个作用于某对象结构中的各元素的操作.它使你能够在不改变各元素的类的前提下定义作用于这些元素的新操作. 2.别名 无 3.动机 考虑一个编译器.他将源程序表示为一个抽象语法树.该编译器须 ...
随机推荐
- _spellmod_aura_on_classmask
-- 修改职业所有技能,公共冷却时间.冷却时间等--小技巧: 根据职业107,108来修改(近期会有改动) `aura` 技能光环ID `spellfamily` 职业代码 `type1` 百分比还是 ...
- ArcGIS Pro开发Web3D应用(2)——地图分屏对比(多屏对比)思路
很多应用中都需要用到地图联动.多屏对比.二三维分屏.大屏显示,有图形可视化的地方就有事件响应触发:鼠标按下.移动.鼠标滚轮,由此触发了地图上坐标或范围的变化,将这些变化发送给另一个地图并响应这些变化, ...
- 【编程语言】extern "C"让C++与C进行混合编程
最近工作不算轻松,现在作为一个项目的负责人统一管理着前端.后端.设计.产品,身上肩负着不小责任,虽然有压力但是对于自己也是一种锻炼.同时自己也在负责整个后端的架构设计,虽然后端经验不是很多,但是自己正 ...
- sysbench工具安装使用
一.sysbench简介 Sysbench是一款开源的.跨平台的.模块化的.多线程的性能测试工具,通过高负载地运行在数据库上,可以执行CPU.内存.线程.IO.数据库等方面的性能测试.用于评估操作系统 ...
- nmap工具简介
nmap参数介绍: -sL:简单列表扫描 -sn:扫描主机,但是不进行端口扫描 -sS:TCP SYN扫描[半开放扫描,扫描速度高且隐蔽性好] -p |-F:扫描端口列表与扫描次序,常用的扫描方式[- ...
- ssh-keygen公钥进行免登
A服务器地址:192.168.1.200,下面简称A B服务器地址:192.168.1.201,下面简称B 1.在A生成密钥对ssh-keygen -t rsa -P ""1执行上 ...
- Docker私有仓库实例
C:\Users\think\.m2\settings.xml文件配置: <?xml version="1.0" encoding="UTF-8"?> ...
- sql server中如何修改视图中的数据?
sql server中如何修改视图中的数据? 我做个测试,视图的数据有标记字段时,如果是这种方式(0 as FlagState),是无法修改的 --创建视图语句 --- create view V_E ...
- request.getParameter()在get和post方法中文乱码问题
乱码原因:Http请求传输时将url以ISO-8859-1编码,服务器收到字节流后默认会以ISO-8859-1编码来解码成字符流(造成中文乱码) post请求: 假设提交请求的jsp页面是UTF-8编 ...
- java 编译
package javacodeforstudy.testcode; public class Helloworld{ public static void main(String[] args) { ...