Visitor Pattern Introduction

类别:Java 点击:0 评论:0 推荐:
Visitor Pattern Introduction

(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