外觀模式(Facade pattern)涉及到子系統(tǒng)的一些類。所謂子系統(tǒng),是為提供一系列相關(guān)的特征(功能)而緊密關(guān)聯(lián)的一組類。例如,一個Account類、Address類和CreditCard類相互關(guān)聯(lián),成為子系統(tǒng)的一部分,提供在線客戶的特征。
在真實的應用系統(tǒng)中,一個子系統(tǒng)可能由很多類組成。子系統(tǒng)的客戶為了它們的需要,需要和子系統(tǒng)中的一些類進行交互。客戶和子系統(tǒng)的類進行直接的交互會導 致客戶端對象和子系統(tǒng)(Figure1)之間高度耦合。任何的類似于對子系統(tǒng)中類的接口的修改,會對依賴于它的所有的客戶類造成影響。
外觀模式(Facade pattern)很適用于在上述情況。外觀模式(Facade pattern)為子系統(tǒng)提供了一個更高層次、更簡單的接口,從而降低了子系統(tǒng)的復雜度和依賴。這使得子系統(tǒng)更易于使用和管理。
外觀是一個能為子系統(tǒng)和客戶提供簡單接口的類。當正確的應用外觀,客戶不再直接和子系統(tǒng)中的類交互,而是與外觀交互。外觀承擔與子系統(tǒng)中類交互的責任。實際上,外觀是子系統(tǒng)與客戶的接口,這樣外觀模式降低了子系統(tǒng)和客戶的耦合度(Figure2).
從Figure2中我們可以看到:外觀對象隔離了客戶和子系統(tǒng)對象,從而降低了耦合度。當子系統(tǒng)中的類進行改變時,客戶端不會像以前一樣受到影響。
盡管客戶使用由外觀提供的簡單接口,但是當需要的時候,客戶端還是可以視外觀不存在,直接訪問子系統(tǒng)中的底層次的接口。這種情況下,它們之間的依賴/耦合度和原來一樣。
例子:
讓我們建立一個應用:
(1) 接受客戶的詳細資料(賬戶、地址和信用卡信息)
(2) 驗證輸入的信息
(3) 保存輸入的信息到相應的文件中。
這個應用有三個類:Account、Address和CreditCard。每一個類都有自己的驗證和保存數(shù)據(jù)的方法。
Listing1: AccountClass
Listing2: Address Class
Listing3: CreditCard Class
??
Figure3: Subsystem Classes to Provide the Necessary Functionality to Validate and Save the Customer Data
在真實的應用系統(tǒng)中,一個子系統(tǒng)可能由很多類組成。子系統(tǒng)的客戶為了它們的需要,需要和子系統(tǒng)中的一些類進行交互。客戶和子系統(tǒng)的類進行直接的交互會導 致客戶端對象和子系統(tǒng)(Figure1)之間高度耦合。任何的類似于對子系統(tǒng)中類的接口的修改,會對依賴于它的所有的客戶類造成影響。
外觀模式(Facade pattern)很適用于在上述情況。外觀模式(Facade pattern)為子系統(tǒng)提供了一個更高層次、更簡單的接口,從而降低了子系統(tǒng)的復雜度和依賴。這使得子系統(tǒng)更易于使用和管理。
外觀是一個能為子系統(tǒng)和客戶提供簡單接口的類。當正確的應用外觀,客戶不再直接和子系統(tǒng)中的類交互,而是與外觀交互。外觀承擔與子系統(tǒng)中類交互的責任。實際上,外觀是子系統(tǒng)與客戶的接口,這樣外觀模式降低了子系統(tǒng)和客戶的耦合度(Figure2).
? ![]() Figure2: Client Interaction with Subsystem Classes after Applying the Facade Pattern |
從Figure2中我們可以看到:外觀對象隔離了客戶和子系統(tǒng)對象,從而降低了耦合度。當子系統(tǒng)中的類進行改變時,客戶端不會像以前一樣受到影響。
盡管客戶使用由外觀提供的簡單接口,但是當需要的時候,客戶端還是可以視外觀不存在,直接訪問子系統(tǒng)中的底層次的接口。這種情況下,它們之間的依賴/耦合度和原來一樣。
例子:
讓我們建立一個應用:
(1) 接受客戶的詳細資料(賬戶、地址和信用卡信息)
(2) 驗證輸入的信息
(3) 保存輸入的信息到相應的文件中。
這個應用有三個類:Account、Address和CreditCard。每一個類都有自己的驗證和保存數(shù)據(jù)的方法。
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,它提供用戶輸入數(shù)據(jù)的用戶界面。
Listing4: Client AccountManager Class
當客戶AccountManage運行的時候,展示的用戶接口如下:
為了驗證和保存輸入的數(shù)據(jù),客戶AccountManager需要:
(1) 建立Account、Address和CreditCard對象。
(2) 用這些對象驗證輸入的數(shù)據(jù)
(3) 用這些對象保存輸入的數(shù)據(jù)。
下面是對象間的交互順序圖:
在這個例子中應用外觀模式是一個很好的設(shè)計,它可以降低客戶和子系統(tǒng)組件(Address、Account和CreditCard)之間的耦合度。應用 外觀模式,讓我們定義一個外觀類CustomerFacade (Figure6 and Listing5)。它為由客戶數(shù)據(jù)處理類(Address、Account和CreditCard)所組成的子系統(tǒng)提供一個高層次的、簡單的接口。
Listing5: CustomerFacade Class
CustomerFacade類以saveCustomData方法的形式提供了業(yè)務層次上的服務。客戶AccountManager不是直接和子系統(tǒng) 的每一個組件交互,而是使用了由CustomFacade對象提供的驗證和保存客戶數(shù)據(jù)的更高層次、更簡單的接口(Figure7).
在新的設(shè)計中,為了驗證和保存客戶數(shù)據(jù),客戶需要:
(1) 建立或獲得外觀對象CustomFacade的一個實例。
(2) 傳遞數(shù)據(jù)給CustomFacade實例進行驗證和保存。
(3) 調(diào)用CustomFacade實例上的saveCustomData方法。
CustomFacade處理創(chuàng)建子系統(tǒng)中必要的對象并且調(diào)用這些對象上相應的驗證、保存客戶數(shù)據(jù)的方法這些細節(jié)問題。客戶不再需要直接訪問任何的子系統(tǒng)中的對象。
Figure8展示了新的設(shè)計的消息流圖:
重要提示:
下面是應用外觀模式的注意事項:
(1) 在設(shè)計外觀時,不需要增加額外的功能。
(2) 不要從外觀方法中返回子系統(tǒng)中的組件給客戶。例如:有一個下面的方法:
CreditCard getCreditCard()
會報漏子系統(tǒng)的細節(jié)給客戶。應用就不能從應用外觀模式中取得最大的好處。
(3)應用外觀的目的是提供一個高層次的接口。因此,外觀方法最適合提供特定的高層次的業(yè)務服務,而不是進行底層次的單獨的業(yè)務執(zhí)行。
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 |
為了驗證和保存輸入的數(shù)據(jù),客戶AccountManager需要:
(1) 建立Account、Address和CreditCard對象。
(2) 用這些對象驗證輸入的數(shù)據(jù)
(3) 用這些對象保存輸入的數(shù)據(jù)。
下面是對象間的交互順序圖:
![]() Figure5: How a Client Would Normally Interact (Directly) with Subsystem Classes to Validate and Save the Customer Data |
在這個例子中應用外觀模式是一個很好的設(shè)計,它可以降低客戶和子系統(tǒng)組件(Address、Account和CreditCard)之間的耦合度。應用 外觀模式,讓我們定義一個外觀類CustomerFacade (Figure6 and Listing5)。它為由客戶數(shù)據(jù)處理類(Address、Account和CreditCard)所組成的子系統(tǒng)提供一個高層次的、簡單的接口。
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方法的形式提供了業(yè)務層次上的服務。客戶AccountManager不是直接和子系統(tǒng) 的每一個組件交互,而是使用了由CustomFacade對象提供的驗證和保存客戶數(shù)據(jù)的更高層次、更簡單的接口(Figure7).
![]() Figure7: Class Association with the Fa?ade Class in Place 。 |
在新的設(shè)計中,為了驗證和保存客戶數(shù)據(jù),客戶需要:
(1) 建立或獲得外觀對象CustomFacade的一個實例。
(2) 傳遞數(shù)據(jù)給CustomFacade實例進行驗證和保存。
(3) 調(diào)用CustomFacade實例上的saveCustomData方法。
CustomFacade處理創(chuàng)建子系統(tǒng)中必要的對象并且調(diào)用這些對象上相應的驗證、保存客戶數(shù)據(jù)的方法這些細節(jié)問題。客戶不再需要直接訪問任何的子系統(tǒng)中的對象。
Figure8展示了新的設(shè)計的消息流圖:
![]() Figure 22.8: In the Revised Design, Clients Interact with the Fa?ade Instance to Interface with the Subsystem |
重要提示:
下面是應用外觀模式的注意事項:
(1) 在設(shè)計外觀時,不需要增加額外的功能。
(2) 不要從外觀方法中返回子系統(tǒng)中的組件給客戶。例如:有一個下面的方法:
CreditCard getCreditCard()
會報漏子系統(tǒng)的細節(jié)給客戶。應用就不能從應用外觀模式中取得最大的好處。
(3)應用外觀的目的是提供一個高層次的接口。因此,外觀方法最適合提供特定的高層次的業(yè)務服務,而不是進行底層次的單獨的業(yè)務執(zhí)行。