概述
在軟件開發系統中,客戶程序經常會與復雜系統的內部子系統之間產生耦合,而導致客戶程序隨著子系統的變化而變化。那麼如何簡化客戶程序與子系統之間的交互接口?如何將復雜系統的內部子系統與客戶程序之間的依賴解耦?這就是要說的Faç ;ade 模式。
意圖
為子系統中的一組接口提供一個一致的界面,Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。[GOF 《設計模式》]
示意圖
門面模式沒有一個一般化的類圖描述,下面是一個示意性的對象圖:
圖1 Faç ;ade模式示意性對象圖
生活中的例子
外觀模式為子系統中的接口定義了一個統一的更高層次的界面,以便於使用。當消費者按照目錄采購時,則體現了一個外觀模式。消費者撥打一個號碼與客服代表聯系,客服代表則扮演了這個"外觀",他包含了與訂貨部、收銀部和送貨部的接口。
圖2使用電話訂貨例子的外觀模式對象圖
Facade模式解說
我們平時的開發中其實已經不知不覺的在用Faç ;ade模式,現在來考慮這樣一個抵押系統,當有一個客戶來時,有如下幾件事情需要確認:到銀行子系統查詢他是否有足夠多的存款,到信用子系統查詢他是否有良好的信用,到貸款子系統查詢他有無貸款劣跡。只有這三個子系統都通過時才可進行抵押。我們先不考慮Faç ;ade模式,那麼客戶程序就要直接訪問這些子系統,分別進行判斷。類結構圖下:
圖3
在這個程序中,我們首先要有一個顧客類,它是一個純數據類,並無任何操作,示意代碼:
//顧客類
public class Customer
{
private string _name;
public Customer(string name)
{
this._name = name;
}
public string Name
{
get { return _name; }
}
}
下面這三個類均是子系統類,示意代碼:
//銀行子系統
public class Bank
{
public bool HasSufficientSavings(Customer c, int amount)
{
Console.WriteLine("Check bank for " + c.Name);
return true;
}
}
//信用子系統
public class Credit
{
public bool HasGoodCredit(Customer c)
{
Console.WriteLine("Check credit for " + c.Name);
return true;
}
}
//貸款子系統
public class Loan
{
public bool HasNoBadLoans(Customer c)
{
Console.WriteLine("Check loans for " + c.Name);
return true;
}
}
來看客戶程序的調用:
//客戶程序
public class MainApp
{
private const int _amount = 12000;
public static void Main()
{
Bank bank = new Bank();
Loan loan = new Loan();
Credit credit = new Credit();
Customer customer = new Customer("Ann McKinsey");
bool eligible = true;
if (!bank.HasSufficientSavings(customer, _amount))
{
eligible = false;
}
else if (!loan.HasNoBadLoans(customer))
{
eligible = false;
}
else if (!credit.HasGoodCredit(customer))
{
eligible = false;
}
Console.WriteLine("\n" + customer.Name + " has been " + (eligible ? "Approved" : "Rejected"));
Console.ReadLine();
}
}
可以看到,在不用Faç ;ade模式的情況下,客戶程序與三個子系統都發生了耦合,這種耦合使得客戶程序依賴於子系統,當子系統變化時,客戶程序也將面臨很多變化的挑戰。一個合情合理的設計就是為這些子系統創建一個統一的接口,這個接口簡化了客戶程序的判斷操作。看一下引入Faç ;ade模式後的類結構圖:
圖4
門面類Mortage的實現如下:
//外觀類
public class Mortgage
{
private Bank bank = new Bank();
private Loan loan = new Loan();
private Credit credit = new Credit();
public bool IsEligible(Customer cust, int amount)
{
Console.WriteLine("{0} applies for {1:C} loan\n",
cust.Name, amount);
bool eligible = true;
if (!bank.HasSufficientSavings(cust, amount))
{
eligible = false;
}
else if (!loan.HasNoBadLoans(cust))
{
eligible = false;
}
else if (!credit.HasGoodCredit(cust))
{
eligible = false;
}
return eligible;
}
}
顧客類和子系統類的實現仍然如下:
//銀行子系統
public class Bank
{
public bool HasSufficientSavings(Customer c, int amount)
{
Console.WriteLine("Check bank for " + c.Name);
return true;
}
}
//信用證子系統
public class Credit
{
public bool HasGoodCredit(Customer c)
{
Console.WriteLine("Check credit for " + c.Name);
return true;
}
}
//貸款子系統
public class Loan
{
public bool HasNoBadLoans(Customer c)
{
Console.WriteLine("Check loans for " + c.Name);
return true;
}
}
//顧客類
public class Customer
{
private string name;
public Customer(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
}
}
而此時客戶程序的實現:
//客戶程序類
public class MainApp
{
public static void Main()
{
//外觀
Mortgage mortgage = new Mortgage();
Customer customer = new Customer("Ann McKinsey");
bool eligable = mortgage.IsEligible(customer, 125000);
Console.WriteLine("\n" + customer.Name +
" has been " + (eligable ? "Approved" : "Rejected"));
Console.ReadLine();
}
}
可以看到引入Faç ;ade模式後,客戶程序只與Mortgage發生依賴,也就是Mortgage屏蔽了子系統之間的復雜的操作,達到了解耦內部子系統與客戶程序之間的依賴。
.NET架構中的Faç ;ade模式
Faç ;ade模式在實際開發中最多的運用當屬開發N層架構的應用程序了,一個典型的N層結構如下:
圖5
在這個架構中,總共分為四個邏輯層,分別為:用戶層UI,業務外觀層Business Faç ;ade,業務規則層Business Rule,數據訪問層Data Access。其中Business Faç ;ade層的職責如下:
l 從“用戶”層接收用戶輸入
l 如果請求需要對數據進行只讀訪問,則可能使用“數據訪問”層
l 將請求傳遞到“業務規則”層
l 將響應從“業務規則”層返回到“用戶”層
l 在對“業務規則”層的調用之間維護臨時狀態
對這一架構最好的體現就是Duwamish示例了。在該應用程序中,有部分操作只是簡單的從數據庫根據條件提取數據,不需要經過任何處理,而直接將數據顯示到網頁上,比如查詢某類別的圖書列表。而另外一些操作,比如計算定單中圖書的總價並根據顧客的級別計算回扣等等,這部分往往有許多不同的功能的類,操作起來也比較復雜。如果采用傳統的三層結構,這些商業邏輯一般是會放在中間層,那麼對內部的這些大量種類繁多,使用方法也各異的不同的類的調用任務,就完全落到了表示層。這樣勢必會增加表示層的代碼量,將表示層的任務復雜化,和表示層只負責接受用戶的輸入並返回結果的任務不太相稱,並增加了層與層之間的耦合程度。於是就引入了一個Faç ;ade層,讓這個Facade來負責管理系統內部類的調用,並為表示層提供了一個單一而簡單的接口。看一下Duwamish結構圖:
圖6
從圖中可以看到,UI層將請求發送給業務外觀層,業務外觀層對請求進行初步的處理,判斷是否需要調用業務規則層,還是直接調用數據訪問層獲取數據。最後由數據訪問層訪問數據庫並按照來時的步驟返回結果到UI層,來看具體的代碼實現。
在獲取商品目錄的時候,Web UI調用業務外觀層:
productSystem = new ProductSystem();
categorySet = productSystem.GetCategories(categoryID);
業務外觀層直接調用了數據訪問層:
public CategoryData GetCategories(int categoryId)
{
//
// Check preconditions
//
ApplicationAssert.CheckCondition(categoryId >= 0,"Invalid Category Id",ApplicationAssert.LineNumber);
//
// Retrieve the data
//
using (Categories accessCategories = new Categories())
{
return accessCategories.GetCategories(categoryId);
}
}
在添加訂單時,UI調用業務外觀層:
public void AddOrder()
{
ApplicationAssert.CheckCondition(cartOrderData != null , "Order requires data", ApplicationAssert.LineNumber);
//Write trace log.
ApplicationLog.WriteTrace("Duwamish7.Web.Cart.AddOrder:\r\nCustomerId: " +
cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[0][OrderData.PKID_FIELD].ToString());
cartOrderData = (new OrderSystem()).AddOrder(cartOrderData);
}
業務外觀層調用業務規則層:
public OrderData AddOrder(OrderData order)
{
//
// Check preconditions
//
ApplicationAssert.CheckCondition(order != null , "Order is required", ApplicationAssert.LineNumber);
(new BusinessRules.Order()).InsertOrder(order);
return order;
}
業務規則層進行復雜的邏輯處理後,再調用數據訪問層:
public bool InsertOrder(OrderData order)
[MSDN]
{
//
// Assume it's good
//
bool isValid = true;
//
// Validate order summary
//
DataRow summaryRow = order.Tables[OrderData.ORDER_SUMMARY_TABLE].Rows[0];
summaryRow.ClearErrors();
if (CalculateShipping(order) != (Decimal )(summaryRow[OrderData.SHIPPING_HANDLING_FIELD]))
{
summaryRow.SetColumnError(OrderData.SHIPPING_HANDLING_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
if (CalculateTax(order) != (Decimal )(summaryRow[OrderData.TAX_FIELD]))
{
summaryRow.SetColumnError(OrderData.TAX_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
//
// Validate shipping info
//
isValid &= IsValidField(order, OrderData.SHIPPING_ADDRESS_TABLE, OrderData.SHIP_TO_NAME_FIELD, 40);
//
// Validate payment info
//
DataRow paymentRow = order.Tables[OrderData.PAYMENT_TABLE].Rows[0];
paymentRow.ClearErrors();
isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_TYPE_FIELD, 40);
isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_NUMBER_FIELD, 32);
isValid &= IsValidField(paymentRow, OrderData.EXPIRATION_DATE_FIELD, 30);
isValid &= IsValidField(paymentRow, OrderData.NAME_ON_CARD_FIELD, 40);
isValid &= IsValidField(paymentRow, OrderData.BILLING_ADDRESS_FIELD, 255);
//
// Validate the order items and recalculate the subtotal
//
DataRowCollection itemRows = order.Tables[OrderData.ORDER_ITEMS_TABLE].Rows;
Decimal subTotal = 0;
foreach (DataRow itemRow in itemRows)
{
itemRow.ClearErrors();
subTotal += (Decimal )(itemRow[OrderData.EXTENDED_FIELD]);
if ((Decimal )(itemRow[OrderData.PRICE_FIELD]) <= 0)
{
itemRow.SetColumnError(OrderData.PRICE_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
if ((short)(itemRow[OrderData.QUANTITY_FIELD]) <= 0)
{
itemRow.SetColumnError(OrderData.QUANTITY_FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
}
//
// Verify the subtotal
//
if (subTotal != (Decimal )(summaryRow[OrderData.SUB_TOTAl _FIELD]))
{
summaryRow.SetColumnError(OrderData.SUB_TOTAl _FIELD, OrderData.INVALID_FIELD);
isValid = false;
}
if ( isValid )
{
using (DataAccess.Orders ordersDataAccess = new DataAccess.Orders())
{
return (ordersDataAccess.InsertOrderDetail (order)) > 0;
}
}
else
return false;
}
效果及實現要點
1.Faç ;ade模式對客戶屏蔽了子系統組件,因而減少了客戶處理的對象的數目並使得子系統使用起來更加方便。
2.Faç ;ade模式實現了子系統與客戶之間的松耦合關系,而子系統內部的功能組件往往是緊耦合的。松耦合關系使得子系統的組件變化不會影響到它的客戶。
3.如果應用需要,它並不限制它們使用子系統類。因此你可以在系統易用性與通用性之間選擇。
適用性
1.為一個復雜子系統提供一個簡單接口。
2.提高子系統的獨立性。
3.在層次化結構中,可以使用Facade模式定義系統中每一層的入口。
總結
Faç ;ade模式注重的是簡化接口,它更多的時候是從架構的層次去看整個系統,而並非單個類的層次。