天天看點

Java設計模式之Adapter模式

通常,客戶類(clients of class)通過類的接口通路它提供的服務。有時,現有的類(existing class)可以提供客戶類的功能需要,但是它所提供的接口不一定是客戶類所期望的。這是由于現有的接口太詳細或者缺乏詳細或接口的名稱與客戶類所查找的不同等諸多不同原因導緻的。

  在這種情況下,現有的接口需要轉化(convert)為客戶類期望的接口,這樣保證了對現有類的重用。如果不進行這樣的轉化,客戶類就不能利用現有類所提供的功能。擴充卡模式(Adapter Pattern)可以完成這樣的轉化。擴充卡模式建議定義一個包裝類,包裝有不相容接口的對象。這個包裝類指的就是擴充卡(Adapter),它包裝的對象就是适配者(Adaptee)。擴充卡提供客戶類需要的接口,擴充卡接口的實作是把客戶類的請求轉化為對适配者的相應接口的調用。換句話說:當客戶類調用擴充卡的方法時,在擴充卡類的内部調用适配者類的方法,這個過程對客戶類是透明的,客戶類并不直接通路适配者類。是以,擴充卡可以使由于接口不相容而不能互動的類可以一起工作(work together)。

  在上面讨論的接口:

  (1) 不是指在JAVA程式設計語言中接口的概念,雖然類的接口可以通過JAVA借擴來定義。

  (2) 不是指由窗體和GUI控件所組成的GUI應用程式的使用者接口。

  (3) 而是指類所暴露的,被其他類調用的程式設計接口,

  類擴充卡(Class Adapter)VS對象擴充卡(Object Adapter)

  擴充卡總體上可以分為兩類:類擴充卡(Class Adapter)VS對象擴充卡(Object Adapter)

  類擴充卡:

  類擴充卡是通過繼承類适配者類(Adaptee Class)實作的,另外類擴充卡實作客戶類所需要的接口。當客戶對象調用擴充卡類方法的時候,擴充卡内部調用它所繼承的适配者的方法。

  對象擴充卡:

  對象擴充卡包含一個擴充卡者的引用(reference),與類擴充卡相同,對象擴充卡也實作了客戶類需要的接口。當客戶對象調用對象擴充卡的方法的時候,對象擴充卡調它所包含的擴充卡者執行個體的适當方法。

  下表是類擴充卡(Class Adapter)和對象擴充卡(Object Adapter)的詳細不同:

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112810359.bmp[/img]

  補充:

  類擴充卡(Class Adapter) 對象擴充卡(Object Adapter)

  基于繼承概念 利用對象合成

  隻能應用在适配者是接口,不能利用它子類的接口,當類擴充卡建立時,它就靜态地與适配者關聯 可以應用在适配者是接口和它的所有子類,因為擴充卡是作為适配者的子類,是以擴充卡可能會重載适配者的一些行為。

  注意:在JAVA中,子類不能重載父類中聲明為final的方法。 不能重載适配者的方法。

  注意:字面上,不能重栽隻是因為沒有繼承。但是擴充卡提供包裝方法可以按需要改變行為。

  客戶類對适配者中聲明為public的接口是可見的, 客戶類和适配者是完全不關聯的,隻有擴充卡才能感覺适配者接口。

  在JAVA應用程式中:

  适用于期待的接口是JAVA接口的形式,而不是抽象地或具體地類的形式。這是因為JAVA程式設計語言隻允許單繼承。是以,類擴充卡設計成适配者的子類。 在JAVA應用程式中:

  适用于當客戶對象期望的接口是抽象類的形式,同時也可以應用于期望接口是Java接口的形式。

  例子:

  讓我們建立一個驗證給定客戶位址的應用。這個應用是作為大的客戶資料管理應用的一部分。

  讓我們定義一個Customer類:

  Customer

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112811971.bmp[/img]

  Figure 20.1: Customer Class

  Listing 20.1: Customer Class

  class Customer {

  public static final String US = "US";

  public static final String CANADA = "Canada";

  private String address;

  private String name;

  private String zip, state, type;

  public boolean isValidAddress() {

  …

  …

  }

  public Customer(String inp_name, String inp_address,

  String inp_zip, String inp_state,

  String inp_type) {

  name = inp_name;

  address = inp_address;

  zip = inp_zip;

  state = inp_state;

  type = inp_type;

  }

  }//end of class

不同的客戶對象建立Customer對象并調用(invoke)isValidAddress方法驗證客戶位址的有效性。為了驗證客戶位址的有效性,Customer類期望利用一個位址驗證類(address validator class),這個驗證類提供了在接口AddressValidator中聲明的接口。

  Listing 20.2: AddressValidator as an Interface

  public interface AddressValidator {

  public boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state);

  }//end of class

  讓我們定義一個USAddress的驗證類,來驗證給定的U.S位址。

  Listing 20.3: USAddress Class

  class USAddress implements AddressValidator {

  public boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state) {

  if (inp_address.trim().length() < 10)

  return false;

  if (inp_zip.trim().length() < 5)

  return false;

  if (inp_zip.trim().length() > 10)

  return false;

  if (inp_state.trim().length() != 2)

  return false;

  return true;

  }

  }//end of class

  USAddress類實作AddressValidator接口,是以Customer對象使用USAddress執行個體作為驗證客戶位址過程的一部分是沒有任何問題的。

  Listing 20.4: Customer Class Using the USAddress Class

  class Customer {

  …

  …

  public boolean isValidAddress() {

  //get an appropriate address validator

  AddressValidator validator = getValidator(type);

  //Polymorphic call to validate the address

  return validator.isValidAddress(address, zip, state);

  }

  private AddressValidator getValidator(String custType) {

  AddressValidator validator = null;

  if (custType.equals(Customer.US)) {

  validator = new USAddress();

  }

  return validator;

  }

  }//end of class

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112813521.bmp[/img]

  Figure 20.2: Customer/USAddress Validator?Class Association

  但是當驗證來自加拿大的客戶時,就要對應用進行改進。這需要一個驗證加拿大客戶位址的驗證類。讓我們假設已經存在一個用來驗證加拿大客戶位址的使用工具類CAAddress。

  從下面的CAAdress類的實作,可以發現CAAdress提供了客戶類Customer類所需要的驗證服務。但是它所提供的接口不用于客戶類Customer所期望的。從下面的CAAdress類的實作,可以發現CAAdress提供了客戶類Customer類所需要的驗證服務。但是它所提供的接口不用于客戶類Customer所期望的。

  Listing 20.5: CAAdress Class with Incompatible Interface

  class CAAddress {

  public boolean isValidCanadianAddr(String inp_address,

  String inp_pcode, String inp_prvnc) {

  if (inp_address.trim().length() < 15)

  return false;

  if (inp_pcode.trim().length() != 6)

  return false;

  if (inp_prvnc.trim().length() < 6)

  return false;

  return true;

  }

  }//end of class

CAAdress類提供了一個isValidCanadianAddr方法,但是Customer期望一個聲明在AddressValidator接口中的isValidAddress方法。

  接口的不相容使得Customer對象利用現有的CAAdress類是困難的。一種意見是改變CAAdress類的接口,但是可能會有其他的應用正在使用CAAdress類的這種形式。改變CAAdress類接口會影響現在使用CAAdress類的客戶。

  應用擴充卡模式,類擴充卡CAAdressAdapter可以繼承CAAdress類實作AddressValidator接口。

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112813645.bmp[/img]

  Figure 20.3: Class Adapter for the CAAddress Class

  Listing 20.6: CAAddressAdapter as a Class Adapter

  public class CAAddressAdapter extends CAAddress

  implements AddressValidator {

  public boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state) {

  return isValidCanadianAddr(inp_address, inp_zip,

  inp_state);

  }

  }//end of class

  因為擴充卡CAAdressAdapter實作了AddressValidator接口,用戶端對象通路擴充卡CAAdressAdapter 對象是沒有任何問題的。當客戶對象調用擴充卡執行個體的isValidAddress方法的時候,擴充卡在内部把調用傳遞給它繼承的 isValidCanadianAddr方法。

  在Customer類内部,getValidator私有方法需要擴充,以至于它可以在驗證加拿大客戶的時候傳回一個 CAAdressAdapter執行個體。傳回的對象是多态的,USAddress和CAAddressAdapter都實作了 AddressValidator接口,是以不用改變。

  Listing 20.7: Customer Class Using the CAAddressAdapter Class

  class Customer {

  …

  …

  public boolean isValidAddress() {

  //get an appropriate address validator

  AddressValidator validator = getValidator(type);

  //Polymorphic call to validate the address

  return validator.isValidAddress(address, zip, state);

  }

  private AddressValidator getValidator(String custType) {

  AddressValidator validator = null;

  if (custType.equals(Customer.US)) {

  validator = new USAddress();

  }

  if (type.equals(Customer.CANADA)) {

  validator = new CAAddressAdapter();

  }

  return validator;

  }

  }//end of class

  CAAddressAdapter設計和對AddressValidator(聲明期望的接口)對象的多态調用使Customer可以利用接口不相容CAAddress類提供的服務。

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112813926.bmp[/img]

  Figure 20.4: Address Validation Application?Using Class Adapter

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112814892.bmp[/img]

  Figure 20.5: Address Validation Message Flow?Using Class Adapter

作為對象擴充卡的位址擴充卡

  當讨論以類擴充卡來實作位址擴充卡時,我們說客戶類期望的AddressValidator接口是Java接口形式。現在,讓我們假設客戶類期望AddressValidator接口是抽象類而不是java接口。因為擴充卡CAAdapter必須提供抽象類 AddressValidatro中聲明的接口,擴充卡必須是AddressValidator抽象類的子類、實作抽象方法。

  Listing 20.8: AddressValidator as an Abstract Class

  public abstract class AddressValidator {

  public abstract boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state);

  }//end of class

  Listing 20.9: CAAddressAdapter Class

  class CAAddressAdapter extends AddressValidator {

  …

  …

  public CAAddressAdapter(CAAddress address) {

  objCAAddress = address;

  }

  public boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state) {

  …

  …

  }

  }//end of class

  因為多繼承在JAVA中不支援,現在擴充卡CAAddressAdapter不能繼承現有的CAAddress類,它已經使用了唯一一次繼承其他類的機會。

  應用對象擴充卡模式,CAAddressAdapter可以包含一個适配者CAAddress的一個執行個體。當擴充卡第一次建立的時候,這個适配者的執行個體通過用戶端傳遞給擴充卡。通常,适配者執行個體可以通過下面兩種方式提供給包裝它的擴充卡。

  (1) 對象擴充卡的用戶端可以傳遞一個适配者的執行個體給擴充卡。這種方式在選擇類的形式上有很大的靈活性,但是用戶端感覺了适配者或者适配過程。這種方法在擴充卡不但需要适配者對象行為而且需要特定狀态時很适合。

  (2) 擴充卡可以自己建立适配者執行個體。這種方法相對來說缺乏靈活性。适用于擴充卡隻需要适配者對象的行為而不需要适配者對象的特定狀态的情況。

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112814229.bmp[/img]

Figure 20.6: Object Adapter for the CAAddress Class

  Listing 20.10: CAAddressAdapter as an Object Adapter

  class CAAddressAdapter extends AddressValidator {

  private CAAddress objCAAddress;

  public CAAddressAdapter(CAAddress address) {

  objCAAddress = address;

  }

  public boolean isValidAddress(String inp_address,

  String inp_zip, String inp_state) {

  return objCAAddress.isValidCanadianAddr(inp_address,

  inp_zip, inp_state);

  }

  }//end of class

  當客戶對象調用CAAddressAdapter(adapter)上的isValidAddress方法時, 擴充卡在内部調用CAAddress(adaptee)上的isValidCanadianAddr方法。

[img]http://java.chinaitlab.com/UploadFiles_8734/201001/20100111112815878.bmp[/img]

Figure 20.7: Address Validation Application?Using Object Adapter

  從這個例子可以看出,擴充卡可以使Customer(client)類通路借口不相容的CAAddress(adaptee)所提供的服務!