1. 程式人生 > >《Java設計模式》之外觀模式

《Java設計模式》之外觀模式

 外觀模式(Facade pattern)涉及到子系統的一些類。所謂子系統,是為提供一系列相關的特徵(功能)而緊密關聯的一組類。例如,一個Account類、Address類和CreditCard類相互關聯,成為子系統的一部分,提供線上客戶的特徵。

  在真實的應用系統中,一個子系統可能由很多類組成。子系統的客戶為了它們的需要,需要和子系統中的一些類進行互動。客戶和子系統的類進行直接的互動會導致客戶端物件和子系統(Figure1)之間高度耦合。任何的類似於對子系統中類的介面的修改,會對依賴於它的所有的客戶類造成影響。

  
Figure1: Client Interaction with Subsystem Classes before Applying the Facade Pattern

  外觀模式(Facade pattern)很適用於在上述情況。外觀模式(Facade pattern)為子系統提供了一個更高層次、更簡單的介面,從而降低了子系統的複雜度和依賴。這使得子系統更易於使用和管理。

  外觀是一個能為子系統和客戶提供簡單介面的類。當正確的應用外觀,客戶不再直接和子系統中的類互動,而是與外觀互動。外觀承擔與子系統中類互動的責任。實際上,外觀是子系統與客戶的介面,這樣外觀模式降低了子系統和客戶的耦合度(Figure2). 

  
Figure2: Client Interaction with Subsystem Classes after Applying the Facade Pattern

  從Figure2中我們可以看到:外觀物件隔離了客戶和子系統物件,從而降低了耦合度。當子系統中的類進行改變時,客戶端不會像以前一樣受到影響。

  儘管客戶使用由外觀提供的簡單介面,但是當需要的時候,客戶端還是可以視外觀不存在,直接訪問子系統中的底層次的介面。這種情況下,它們之間的依賴/耦合度和原來一樣。 

  例子:

  讓我們建立一個應用:

  (1) 接受客戶的詳細資料(賬戶、地址和信用卡資訊)

  (2) 驗證輸入的資訊

  (3) 儲存輸入的資訊到相應的檔案中。

  這個應用有三個類:Account、Address和CreditCard。每一個類都有自己的驗證和儲存資料的方法。

  Listing1: AccountClass 


public class Account { 
 String firstName; 
 String lastName; 
 final String ACCOUNT_DATA_FILE = "AccountData.txt"; 
 public Account(String fname, String lname) { 
  firstName = fname; 
  lastName = lname; 
 } 
 public boolean isValid() { 
  /* 
  Let's go with simpler validation 
  here to keep the example simpler. 
  */ 
  … 
  … 
 } 
 public boolean save() { 
  FileUtil futil = new FileUtil(); 
  String dataLine = getLastName() + ”," + getFirstName(); 
  return futil.writeToFile(ACCOUNT_DATA_FILE, dataLine,true, true); 
 } 
 public String getFirstName() { 
  return firstName; 
 } 
 public String getLastName() { 
  return lastName; 
 } 
}

  Listing2: Address Class 

public class Address { 
 String address; 
 String city; 
 String state; 
 final String ADDRESS_DATA_FILE = "Address.txt"; 
 public Address(String add, String cty, String st) { 
  address = add; 
  city = cty; 
  state = st; 
 } 
 public boolean isValid() { 
  /* 
  The address validation algorithm 
  could be complex in real-world 
  applications. 
  Let's go with simpler validation 
  here to keep the example simpler. 
  */ 
  if (getState().trim().length() < 2) 
   return false; 
  return true; 
 } 
 public boolean save() { 
  FileUtil futil = new FileUtil(); 
  String dataLine = getAddress() + ”," + getCity() + ”," + getState(); 
  return futil.writeToFile(ADDRESS_DATA_FILE, dataLine,true, true); 
 } 
 public String getAddress() { 
  return address; 
 } 
 public String getCity() { 
  return city; 
 } 
 public String getState() { 
  return state; 
 } 
}

  Listing3: CreditCard Class 

public class CreditCard { 
 String cardType; 
 String cardNumber; 
 String cardExpDate; 
 final String CC_DATA_FILE = "CC.txt"; 
 public CreditCard(String ccType, String ccNumber, 
 String ccExpDate) { 
  cardType = ccType; 
  cardNumber = ccNumber; 
  cardExpDate = ccExpDate; 
 } 
 public boolean isValid() { 
  /* 
  Let's go with simpler validation 
  here to keep the example simpler. 
  */ 
  if (getCardType().equals(AccountManager.VISA)) { 
   return (getCardNumber().trim().length() == 16); 
  } 
  if (getCardType().equals(AccountManager.DISCOVER)) { 
   return (getCardNumber().trim().length() == 15); 
  } 
  if (getCardType().equals(AccountManager.MASTER)) { 
   return (getCardNumber().trim().length() == 16); 
  } 
  return false; 
 } 
 public boolean save() { 
  FileUtil futil = new FileUtil(); 
  String dataLine = getCardType() + ,”" + getCardNumber() + ”," + getCardExpDate(); 
  return futil.writeToFile(CC_DATA_FILE, dataLine, true, true); 
 } 
 public String getCardType() { 
  return cardType; 
 } 
 public String getCardNumber() { 
  return cardNumber; 
 } 
 public String getCardExpDate() { 
  return cardExpDate; 
 } 
}

  
Figure3: Subsystem Classes to Provide the Necessary Functionality to Validate and Save the Customer Data


 讓我們建立一個客戶AccountManager,它提供使用者輸入資料的使用者介面。

  Listing4: Client AccountManager Class 

public class AccountManager extends JFrame { 
 public static final String newline = "\n"; 
 public static final String VALIDATE_SAVE = "Validate & Save"; 
 … 
 … 
 public AccountManager() { 
  super(" Facade Pattern - Example "); 
  cmbCardType = new JComboBox(); 
  cmbCardType.addItem(AccountManager.VISA); 
  cmbCardType.addItem(AccountManager.MASTER); 
  cmbCardType.addItem(AccountManager.DISCOVER); 
  … 
  … 
  //Create buttons 
  JButton validateSaveButton = new JButton(AccountManager.VALIDATE_SAVE); 
  … 
  … 
 } 
 public String getFirstName() { 
  return txtFirstName.getText(); 
 } 
 … 
 … 
}//End of class AccountManager

  當客戶AccountManage執行的時候,展示的使用者介面如下:

  
Figure4: User Interface to Enter the Customer Data

  為了驗證和儲存輸入的資料,客戶AccountManager需要:

  (1) 建立Account、Address和CreditCard物件。 

  (2) 用這些物件驗證輸入的資料

  (3) 用這些物件儲存輸入的資料。

  下面是物件間的互動順序圖:

 
Figure5: How a Client Would Normally Interact (Directly) with Subsystem Classes to Validate and Save the Customer Data

  在這個例子中應用外觀模式是一個很好的設計,它可以降低客戶和子系統元件(Address、Account和CreditCard)之間的耦合度。應用外觀模式,讓我們定義一個外觀類CustomerFacade (Figure6 and Listing5)。它為由客戶資料處理類(Address、Account和CreditCard)所組成的子系統提供一個高層次的、簡單的介面。 

CustomerFacade 
address:String 
city:String 
state:String 
cardType:String 
cardNumber:String 
cardExpDate:String 
fname:String 
lname:String 
setAddress(inAddress:String) 
setCity(inCity:String) 
setState(inState:String) 
setCardType(inCardType:String) 
setCardNumber(inCardNumber:String) 
setCardExpDate(inCardExpDate:String) 
setFName(inFName:String) 
setLName(inLName:String) 
saveCustomerData()

  
Figure6: Facade Class to Be Used by the Client in the Revised Design

  Listing5: CustomerFacade Class 

public class CustomerFacade { 
 private String address; 
 private String city; 
 private String state; 
 private String cardType; 
 private String cardNumber; 
 private String cardExpDate; 
 private String fname; 
 private String lname; 
 public void setAddress(String inAddress) { 
  address = inAddress; 
 } 
 public void setCity(String inCity) { 
  city = inCity; 
 } 
 public void setState(String inState) { 
  state = inState; 
 } 
 public void setFName(String inFName) { 
  fname = inFName; 
 } 
 public void setLName(String inLName) { 
  lname = inLName; 
 } 
 public void setCardType(String inCardType) { 
  cardType = inCardType; 
 } 
 public void setCardNumber(String inCardNumber) { 
  cardNumber = inCardNumber; 
 } 
 public void setCardExpDate(String inCardExpDate) { 
  cardExpDate = inCardExpDate; 
 } 
 public boolean saveCustomerData() { 
  Address objAddress; 
  Account objAccount; 
  CreditCard objCreditCard; 
  /* 
   client is transparent from the following 
   set of subsystem related operations. 
  */ 
  boolean validData = true; 
  String errorMessage = ""; 
  objAccount = new Account(fname, lname); 
  if (objAccount.isValid() == false) { 
   validData = false; 
   errorMessage = "Invalid FirstName/LastName"; 
  } 
  objAddress = new Address(address, city, state); 
  if (objAddress.isValid() == false) { 
   validData = false; 
   errorMessage = "Invalid Address/City/State"; 
  } 
  objCreditCard = new CreditCard(cardType, cardNumber, cardExpDate); 
  if (objCreditCard.isValid() == false) { 
   validData = false; 
   errorMessage = "Invalid CreditCard Info"; 
  } 
  if (!validData) { 
   System.out.println(errorMessage); 
   return false; 
  } 
  if (objAddress.save() && objAccount.save() && objCreditCard.save()) { 
   return true; 
  } else { 
   return false; 
  } 
 } 
}

  CustomerFacade類以saveCustomData方法的形式提供了業務層次上的服務。客戶AccountManager不是直接和子系統的每一個元件互動,而是使用了由CustomFacade物件提供的驗證和儲存客戶資料的更高層次、更簡單的介面(Figure7).

 
Figure7: Class Association with the Fa?ade Class in Place 。

  在新的設計中,為了驗證和儲存客戶資料,客戶需要:

  (1) 建立或獲得外觀物件CustomFacade的一個例項。

  (2) 傳遞資料給CustomFacade例項進行驗證和儲存。

  (3) 呼叫CustomFacade例項上的saveCustomData方法。

  CustomFacade處理建立子系統中必要的物件並且呼叫這些物件上相應的驗證、儲存客戶資料的方法這些細節問題。客戶不再需要直接訪問任何的子系統中的物件。

  Figure8展示了新的設計的訊息流圖:

 
Figure 22.8: In the Revised Design, Clients Interact with the Fa?ade Instance to Interface with the Subsystem

重要提示

  下面是應用外觀模式的注意事項:

  (1) 在設計外觀時,不需要增加額外的功能。

  (2) 不要從外觀方法中返回子系統中的元件給客戶。例如:有一個下面的方法:

  CreditCard getCreditCard() 

  會報漏子系統的細節給客戶。應用就不能從應用外觀模式中取得最大的好處。

  (3)應用外觀的目的是提供一個高層次的介面。因此,外觀方法最適合提供特定的高層次的業務服務,而不是進行底層次的單獨的業務執行。

以上是一個比較全面的例子,另外,為了加深理解我們繼續學習下面的內容。

 相關角色:

         1.外觀(Facade)角色:客戶端可以呼叫這個角色的方法。此角色知曉相關的子系統的功能和責任。

         2.子系統角色:可以同時有一個或者多個子系統。每一個子系統都不是一個單獨的類,而是一個類的集合。每一個子系統都可以被客戶端直接呼叫,或者被外觀角色呼叫。

    適用情況:

         1.為複雜的子系統提供一個簡單的介面;

         2.客戶程式與抽象類的實現部分之間存在著很大的依賴性;

         3.構建一個層次結構的子系統時,適用外觀模式定義子系統中每層的入口點。

    外觀模式的簡單實現:

程式碼:

Camara.java

package facade;

public class Camara {
	public void turnOn()
	{
		System.out.println("開啟攝像頭!");
	}
	
	public void turnOff()
	{
		System.out.println("關閉攝像頭!");
	}
}
 

Light.java
package facade;

public class Light {
	public void turnOn()
	{
		System.out.println("開燈!");
	}
	
	public void turnOff()
	{
		System.out.println("關燈!");
	}
}
Sensor.java
package facade;

public class Sensor {
	public void activate()
	{
		System.out.println("開啟感應器!");
	}
	
	public void deactivate()
	{
		System.out.println("關閉感應器!");
	}
}

MyFacade.java
package facade;

public class MyFacade {
	private static Camara c1, c2;
	private static Light l1, l2, l3;
	private static Sensor s;
	
	static
	{
		c1 = new Camara();
		c2 = new Camara();
		l1 = new Light();
		l2 = new Light();
		l3 = new Light();
		s = new Sensor();
	}
	
	public static void activate()
	{
		c1.turnOn();
		c2.turnOn();
		
		l1.turnOn();
		l2.turnOn();
		l3.turnOn();
		
		s.activate();
	}
	
	public static void deactivate()
	{
		c1.turnOff();
		c2.turnOff();
		
		l1.turnOff();
		l2.turnOff();
		l3.turnOff();
		
		s.deactivate();
	}
}

ClientTest.java
package facade;

public class ClientTest {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//開啟
		MyFacade.activate();
		//關閉
		MyFacade.deactivate();
	}

}


 實際應用中,我們在對付一些老舊的code(尤其是將C的程式碼轉成C++程式碼)或者即便不是老舊code,但涉及多個子系統時,除了重寫全部程式碼
    (對於老舊code而言),我們還可能採用這樣一種策略:重新進行類的設計,將原來分散在原始碼中的類/結構及方法重新組合,形成新的、統一的介面,
    供上層應用使用。
        這在某種意義上與Adapter及Proxy有類似之處,但是,Proxy(代理)注重在為Client-Subject提供一個訪問的中間層,如CORBA可為應
    用程式提供透明訪問支援,使應用程式無需去考慮平臺及網路造成的差異及其它諸多技術細節;Adapter(介面卡)注重對介面的轉換與調整;而
    Facade所面對的往往是多個類或其它程式單元,通過重新組合各類及程式單元,對外提供統一的介面/介面。
Facade模式應用
    在遇到以下情況使用Facade模式:
          1、當你要為一個複雜子系統提供一個簡單介面時。子系統往往因為不斷演化而變得越來越複雜。大多數模式使用時都會產生更多更小的類。這使得子系
            統更具可重用性,也更容易對子系統進行定製,但這也給那些不需要定製子系統的使用者帶來一些使用上的困難。
          Facade可以提供一個簡單的預設檢視,這一檢視對大多數使用者來說已經足夠,而那些需要更多的可定製性的使用者可以越過Facade層。
          2、客戶程式與抽象類的實現部分之間存在著很大的依賴性。引入Facade將這個子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移
        植性。
          3、當你需要構建一個層次結構的子系統時,使用Facade模式定義子系統中每層的入口點,如果子系統之間是相互依賴的,你可以讓它們僅通過Facade
        進行通訊,從而簡化了它們之間的依賴關係。

Facade模式優缺點
    Facade模式有下面一些優點:
      1、它對客戶遮蔽子系統元件,因而減少了客戶處理的物件的數目並使得子系統使用起來更加方便。
      2、它實現了子系統與客戶之間的鬆耦合關係,而子系統內部的功能元件往往是緊耦合的。
      鬆耦合關係使得子系統的元件變化不會影響到它的客戶。Facade模式有助於建立層次結構系統,也有助於對物件之間的依賴關係分層。Facade模式可以
    消除複雜的迴圈依賴關係。這一點在客戶程式與子系統是分別實現的時候尤為重要。
      在大型軟體系統中降低編譯依賴性至關重要。在子系統類改變時,希望儘量減少重編譯工作以節省時間。用Facade可以降低編譯依賴性,限制重要系統中較
    小的變化所需的重編譯工作。Facade模式同樣也有利於簡化系統在不同平臺之間的移植過程,因為編譯一個子系統一般不需要編譯所有其他的子系統。

      3、如果應用需要,它並不限制它們使用子系統類。因此你可以在系統易用性和通用性之間加以選擇。

圖例項:


package design.facade;


/**
 * 檔名稱:ServiceA.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public interface ServiceA {
	/**
	 * ServiceA 的A方法 
	 * */
	public void methodA() ;
}






package design.facade;


/**
 * 檔名稱:ServiceAImpl.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public class ServiceAImpl implements ServiceA {


	/* (non-Javadoc)
	 * @see design.facade.ServiceA#methodA()
	 */
	@Override
	public void methodA() {
		System.out.println( "methodA--> is runing" ); 
	}


}






package design.facade;


/**
 * 檔名稱:ServiceB.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public interface ServiceB {
	/**
	 * ServiceB 的B方法
	 * */
	public void methodB() ;
}






package design.facade;


/**
 * 檔名稱:ServiceAImpl.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public class ServiceBImpl implements ServiceB {
	


	/* (non-Javadoc)
	 * @see design.facade.ServiceA#methodA()
	 */
	@Override
	public void methodB() {
		System.out.println( "methodB--> is runing" ); 
	}


}






package design.facade;


/**
 * 檔名稱:ServiceC.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public interface ServiceC {
	/**
	 * ServiceC 的C方法
	 * */
	public void methodC() ;  
}






package design.facade;


/**
 * 檔名稱:ServiceAImpl.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public class ServiceCImpl implements ServiceC {
	


	/* (non-Javadoc)
	 * @see design.facade.ServiceA#methodA()
	 */
	@Override
	public void methodC() {
		System.out.println( "methodC--> is runing" );  
	}


}






package design.facade;
/**
 * 檔名稱:Facade.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * 
 * 外觀模式 核心類
 * */


public class Facade {
	ServiceA sa;
	ServiceB sb;
	ServiceC sc;


	public Facade() {
		sa = new ServiceAImpl();
		sb = new ServiceBImpl();
		sc = new ServiceCImpl();
	}


	public void methodA() {
		sa.methodA();
		sb.methodB();
	}


	public void methodB() {
		sb.methodB();
		sc.methodC();
	}


	public void methodC() {
		sc.methodC();
		sa.methodA();
	}
	
}






package design.facade;


/**
 * 檔名稱:Client.java
 * 建立人:Fei Wong
 * 建立時間: Jun 29, 2012
 * 電子郵箱:[email protected]
 * */


public class Client {


	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ServiceA sa = new ServiceAImpl();
		  ServiceB sb = new ServiceBImpl();
		  sa.methodA();
		  sb.methodB();
		  System.out.println("=====================");
		  Facade f = new Facade();
		  f.methodA();
		  f.methodB();
		  f.methodC() ;
	}


}


本文借鑑文章:

http://dev.yesky.com/203/2175203.shtml

http://blog.csdn.net/hfmbook/article/details/7702642

http://liyf155.iteye.com/blog/1189789