Visitor Pattern Introduction (轉)

worldblog發表於2007-12-12
Visitor Pattern Introduction (轉)[@more@]

:namespace prefix = o ns = "urn:schemas--com::office" />

(wang hailong)

Visitor Pattern可能是設計中最複雜的模式了。Visitor Pattern從Double Dispatch Pattern派生而來,由Double一詞可見其複雜度。

Visitor Pattern,顧名思義,有訪問者和被訪問者,既然,以訪問者命名,那麼,主要的工作都是訪問者來做。

本文不從設計入手,而從一個日常生產生活的例子入手,來解釋Visitor Pattern。

一個生產高能電池的廠家,下設一個客戶服務部門,其主要任務之一就是收集的反饋意見,以便改進產品功能和服務質量。

以前,客戶服務部門只是採用發放調查問卷的方式。調查問卷的問題千篇一律,不能針對特殊使用者的興趣點,尤其一些集體使用者,不會認真對待這些問卷,懶得把問卷髮到具體的使用者手裡,而且,很多使用者不願意花時間把調查問卷寄回。調查結果不全面,不真實,幾乎沒有任何效果。

之後,個性化服務、CRM客戶關係管理等概念興起,客戶服務部門引入了一套CRM客戶(使用者)關係管理,對客戶資訊進行管理。針對不同使用者的特點,採用不同的調查方式。採用電話、E-、傳真、問卷、登門拜訪等多種方式,對使用者進行調查訪問。針對一些集體使用者,比如,企事業單位使用者,客戶服務人員首先訪問聯絡該單位集體,安排好時間之後,客戶服務人員具體訪問每一個具體使用者,這些使用者接受客戶服務人員的訪問,給出親身使用電池產品的第一手資料。

還有的情況,集體單位下面還有子單位,比如,公司下面有子公司。那麼,遵循同樣的流程,分別訪問下面的子公司,這些子公司接受客戶服務人員的訪問,安排好時間之後,客戶服務人員具體訪問每一個具體使用者,這些使用者接受客戶服務人員的訪問,給出親身使用電池產品的第一手資料。

我們可以看到,這是一個典型的Visitor Pattern。客戶服務部門就是Visitor,不同型別的使用者就是被訪問者。客戶服務部門(Visitor)幾乎做了所有的工作,儘量不給使用者增加負擔。

下面給出這個例子的示意程式碼。

// 客戶服務部門類,

// 總結CRM客戶關係管理系統的客戶型別資訊,定義以下的方法

class ServiceDepartment{

  // 訪問方式

  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);

  }

}

後記 為什麼寫這篇文章?

以前看到一本書,講述。作者解釋Socket的時候舉了一個接聽電話的例子,講述的很明白。

1.你要接聽電話,首先你要有一個電話,所以,第一步,create a Socket. 這裡,Socket就是你的電話。

2.你還要有一個電話號碼,對於你的Socket來說,你的和埠號就是電話號碼,所以,第二步,bind to a port.

3.你還要等在電話旁邊,等電話鈴響,listen to the port.

4.別人要給你打電話,他(她)也要有一個電話,他需要create a Socket。

5.他需要撥打你的電話號碼,connect to your 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作為一個日常生產生活的場景描述出來。

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992365/,如需轉載,請註明出處,否則將追究法律責任。

相關文章