(wang hailong)
Visitor Pattern可能是设计模式中最复杂的模式了。Visitor Pattern从Double Dispatch Pattern派生而来,由Double一词可见其复杂度。
Visitor Pattern,顾名思义,有访问者和被访问者,既然,以访问者命名,那么,主要的工作都是访问者来做。
本文不从程序设计入手,而从一个日常生产生活的例子入手,来解释Visitor Pattern。
一个生产高能电池的厂家,下设一个客户服务部门,其主要任务之一就是收集用户的反馈意见,以便改进产品功能和服务质量。
以前,客户服务部门只是采用发放调查问卷的方式。调查问卷的问题千篇一律,不能针对特殊用户的兴趣点,尤其一些集体用户,不会认真对待这些问卷,懒得把问卷发到具体的使用者手里,而且,很多用户不愿意花时间把调查问卷寄回。调查结果不全面,不真实,几乎没有任何效果。
之后,个性化服务、CRM客户关系管理等概念兴起,客户服务部门引入了一套CRM客户(用户)关系管理系统,对客户信息进行管理。针对不同用户的特点,采用不同的调查方式。采用电话、E-Mail、传真、问卷、登门拜访等多种方式,对用户进行调查访问。针对一些集体用户,比如,企事业单位用户,客户服务人员首先访问联系该单位集体,安排好时间之后,客户服务人员具体访问每一个具体用户,这些用户接受客户服务人员的访问,给出亲身使用电池产品的第一手资料。
还有的情况,集体单位下面还有子单位,比如,公司下面有子公司。那么,遵循同样的流程,分别访问下面的子公司,这些子公司接受客户服务人员的访问,安排好时间之后,客户服务人员具体访问每一个具体用户,这些用户接受客户服务人员的访问,给出亲身使用电池产品的第一手资料。
我们可以看到,这是一个典型的Visitor Pattern。客户服务部门就是Visitor,不同类型的用户就是被访问者。客户服务部门(Visitor)几乎做了所有的工作,尽量不给用户增加负担。
下面给出这个例子的示意代码。
// 客户服务部门类,
// 总结CRM客户关系管理系统的客户类型信息,定义以下的方法
class ServiceDepartment{
// Email访问方式
private Email sendEmail(…){
…
}
// 电话访问方式
private Answer callPhone(…){
…
}
// 访问用户。
// 这是ServiceDepartment类的入口点方法。
// 注意,参照下面的User的定义代码,User是一个接口类型
public void visitUser(User aUser){
user.accept(this);
}
// 访问习惯Email访问的用户
public void visitEmailUser (EmailUser anEmailUser){
// send email to email user
Email reply = anEmailUser.replyEmail( this.sendEmail() );
// put anwer to database
}
// 访问习惯电话访问的用户
public void visitPhoneUser (PhoneUser aPhoneUser){
// call phone user
Answer answer = A.answerPhone( this. callPhone () );
// put answer to database
}
// 访问公司用户
public void visitCompanyUser( CompanyUser companyUser){
// visit company user
List userList = companyUser.getUserList();
for each user in userList{
// 访问每个用户,每个用户都接受访问。
Visitor.visitUser(user);
// 注意,这里User的类型有可能是CompanyUser类型。
// 这时形成对子公司用户的递归调用。
}
}
}
// 以下列出每个用户类的代码,
// 因为用户是被访问者,负担很少。所以,每个用户的方法都很简单。
// 公共接口类,每个用户类都应该实现这个接口。
public interface User{
// 接受客户服务部门的访问
public void accept(ServiceDepartment visitor);
};
// Email user 类
public class EmailUser implements User {
// 用Email反馈
public Email replayEmail(Email question){
…
}
// 接受客户服务部门的访问
public void accept(ServiceDepartment visitor){
visitor.visitEmailUser(this);
}
};
// Phone user 类
public class PhoneUser implements User{
// 接听电话,进行反馈
public Answer answerPhone(Phone question){
…
}
// 接受客户服务部门的访问
public void accept(ServiceDepartment visitor){
visitor.visitPhoneUser(this);
}
};
// company user 类
public class CompanyUser implements User {
// 接受客户服务部门的访问
public void accept(ServiceDepartment visitor){
visitor.visitCompanyUser(this);
}
};
下面给出一个使用上述模式的例子。
public class TestMain{
public void main(String[] args){
// create a visitor
ServiceDepartmant visitor = new ServiceDepartmant();
// 从数据库中取得所有的用户信息
// 这些用户的类型,有可能是PhoneUser, EmailUser,还有可能是CompanyUser。
for each user created from database {
// 访问每一个用户,用户接受访问,给出反馈,存放到数据库中
visitor.visitUser(user);
}
}
}
好了,所有的示意代码都在这里了。现在,我们考虑问题的变化和扩展。毕竟,设计模式的目的就是为了让变化的部分越小越好,越简单越好。
客户服务部门引入CRM客户关系管理系统,就是为了更好地对应客户(用户)信息的变化。
过了一段时间,CRM客户关系管理系统加入了一个新用户的信息,这个用户习惯使用传真回答调查问卷。这时,我们多了一个用户类,FaxUser。我们需要对上述的ServiceDepartment类(visitor类)进行扩展。
第一种方法是,直接修改ServiceDepartment类,增加一个visitFaxUser(FaxUser)方法。这种方法比较直观,但是需要修改以前的代码。
public class ServiceDepartment{
… // 以前的代码
// 增加一个新的方法,发送传真
private Fax sendFax(…){
…
}
// 增加一个新的方法,访问习惯传真的用户
void visitFaxUser(FaxUser aFaxUser){
Fax fax = aFaxUser.replyFax(this.sendFax());
// 把fax的信息存放到数据库
}
};
这时,新增的FaxUser的代码如下:
public class FaxUser implements User{
// 用传真反馈
public Fax replyFax(Fax fax){
…
}
// 接受客户服务部门的访问
public void accept(ServiceDepartment visitor){
visitor.visitFaxUser(this);
}
}
第二种方法是,继承ServiceDepartment类,定义一个新类ExtendedServiceDepartment,增加一个visitFaxUser(FaxUser)方法。这种方法的好处是不用修改以前的代码,但是,新增加的User类,需要知道新类ExtendedServiceDepartment的定义。
下面给出示意代码,ExtendedServiceDepartment类。
public class ExtendedServiceDepartment extends ServiceDepartment{
// 增加一个新的方法,发送传真
private Fax sendFax(…){
…
}
// 增加一个新的方法,访问习惯传真的用户
void visitFaxUser(FaxUser aFaxUser){
Fax fax = aFaxUser.replyFax(this.sendFax());
// 把fax的信息存放到数据库
}
};
这时,新增的FaxUser的代码如下:
public class FaxUser implements User{
// 用传真反馈
public Fax replyFax(Fax fax){
…
}
// 接受客户服务部门的访问
public void accept(ExtendedServiceDepartment visitor){
visitor.visitFaxUser(this);
}
}
后记 为什么写这篇文章?以前看到一本书,讲述TCP/IP编程。作者解释Socket的时候举了一个接听电话的例子,讲述的很明白。
1.你要接听电话,首先你要有一个电话,所以,第一步,create a Socket. 这里,Socket就是你的电话。
2.你还要有一个电话号码,对于你的Socket来说,你的IP地址和端口号就是电话号码,所以,第二步,bind to a port.
3.你还要等在电话旁边,等电话铃响,listen to the port.
4.别人要给你打电话,他(她)也要有一个电话,他需要create a Socket。
5.他需要拨打你的电话号码,connect to your IP address and port.
6.他的电话来了,你要接听他的电话,accept his connection。这时,还有来电显示,你知道他的电话号码(IP地址)。
7.你同时能接听几个打来的电话,你和他们开始通话。read and write.
8.通话结束,你说,“你先挂电话吧,我还要和别人讲话。”他把电话挂了,close his Socket。
看了这段之后,我很受启发。后来写了一篇文章《Design Pattern Introduction》,提到Observer Pattern,举了邮件订阅,手机短信订阅的例子。
Visitor Pattern,是一个比较复杂的设计模式。有很多关于Visitor Pattern的争论,有些人建议使用,有些人建议不使用。这里,我多费些笔墨,把Visitor Pattern作为一个日常生产生活的场景描述出来。
本文地址:http://com.8s8s.com/it/it17950.htm