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.动机 考虑一个编译器.他将源程序表示为一个抽象语法树.该编译器须 ...
随机推荐
- 常量(constant)
在java语言中,主要用final来定义一个常量.常量一旦被初始化不能更改其值. 常量:大写字母和下划线:MAX_VALUE final double PI = 3.14; PI = 3.15;//编 ...
- leecode第二百三十六题(二叉树的最近公共祖先)
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode ...
- Qt中隐藏滚动条重新实现鼠标滚轮事件wheelEvent
delta()已经被弃用了,QT5中用的是angleDelta(),计算的时候取angleDelta().y()值. #重载方法wheelEvent(self,event),即滚轮事件方法 #---- ...
- QSS 记录
1.border-style 属性分别有 none 定义无边框. hidden 与 "none" 相同.不过应用于表时除外,对于表,hidden 用于解决边框冲突. dotted ...
- c++ 查缺补漏
c++句柄 win句柄保存对象的实时地址(对象消失,句柄消失).指针保存固定地址(对象消失,内存泄漏) 超简单句柄类 指针型句柄 管理图书类句柄 c++ 枚举 enum Suit { Diamonds ...
- useragent大全
分享几个常见的User-Agent,复制粘贴过来的,谢谢原创. window.navigator.userAgent 1) Chrome Win7: Mozilla/5.0 (Windows NT 6 ...
- nodejs 从部署到域名访问
一.Node.js 安装在Ubuntu上 用如下代码下载nodejs 8.x最新版并安装,npm 也会随着一起安装 curl -sL https://deb.nodesource.com/setup_ ...
- jq里验证插件的自定义方法Jquery.validator.addMethod()示例
最近写验证的时候感觉原生的验证谢了一遍又一遍,就想到了“不要重复造轮子,学会管理自己的工具库”这句名言,于是尝试用jq的validator. 用过又发现需要自定义方法去验证,于是去查官网,写了Jque ...
- Vagrant 入门指南
https://blog.csdn.net/qianghaohao/article/details/80038096 https://blog.csdn.net/happyhorizion/artic ...
- hadoop hdfs 数据迁移到其他集群
# hadoop fs -cat /srclist Warning: $HADOOP_HOME is deprecated. hdfs://sht-sgmhadoopcm-01:9011/jdk-6u ...