最近在看設計模式相關的書,感覺這篇文章對Facade模式的分析甚至比書上還好,說以就轉了這篇文章,不過這篇文章有些地方把Facade模式寫成了faade模式,大家看時注意一下。
動機(Motivate):
在軟件開發系統中,客戶程序經常會與複雜系統的內部子系統之間產生耦合,而導致客戶程序隨着子系統的變化而變化。那麼如何簡化客戶程序與子系統之間的交互接口?如何將複雜系統的內部子系統與客戶程序之間的依賴解耦?
意圖(Intent):
爲子系統中的一組接口提供一個一致的界面, Facade 模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
--------《設計模式》GOF
結構圖(Struct):
適用性:
1 .爲一個複雜子系統提供一個簡單接口。
2 .提高子系統的獨立性。
3 .在層次化結構中,可以使用 Facade 模式定義系統中每一層的入口。
生活中的例子:
代碼實現:
我們平時的開發中其實已經不知不覺的在用 Faade 模式,現在來考慮這樣一個抵押系統,當有一個客戶來時,有如下幾件事情需要確認:到銀行子系統查詢他是否有足夠多的存款,到信用子系統查詢他是否有良好的信用,到貸款子系統查詢他有無貸款劣跡。只有這三個子系統都通過時纔可進行抵押。我們先不考慮 Faade 模式,那麼客戶程序就要直接訪問這些子系統,分別進行判斷。類結構圖下:
在這個程序中,我們首先要有一個顧客類,它是一個純數據類,並無任何操作,示意代碼:
1 // 顧客類
2 public class Customer
3 {
4 private string _name;
5
6 public Customer( string name)
7 {
8 this ._name = name;
9 }
10
11 public string Name
12 {
13 get { return _name; }
14 }
15 }
下面這三個類均是子系統類,示意代碼:
1 // 銀行子系統
2 public class Bank
3 {
4 public bool HasSufficientSavings(Customer c, int amount)
5 {
6 Console.WriteLine( " Check bank for " + c.Name);
7 return true ;
8 }
9 }
10
11 // 信用子系統
12 public class Credit
13 {
14 public bool HasGoodCredit(Customer c)
15 {
16 Console.WriteLine( " Check credit for " + c.Name);
17 return true ;
18 }
19 }
20
21 // 貸款子系統
22 public class Loan
23 {
24 public bool HasNoBadLoans(Customer c)
25 {
26 Console.WriteLine( " Check loans for " + c.Name);
27 return true ;
28 }
29 }
看客戶程序的調用:
1 // 客戶程序
2 public class MainApp
3 {
4 private const int _amount = 12000 ;
5
6 public static void Main()
7 {
8 Bank bank = new Bank();
9 Loan loan = new Loan();
10 Credit credit = new Credit();
11
12 Customer customer = new Customer( " Ann McKinsey " );
13
14 bool eligible = true ;
15
16 if ( ! bank.HasSufficientSavings(customer, _amount))
17 {
18 eligible = false ;
19 }
20 else if ( ! loan.HasNoBadLoans(customer))
21 {
22 eligible = false ;
23 }
24 else if ( ! credit.HasGoodCredit(customer))
25 {
26 eligible = false ;
27 }
28
29 Console.WriteLine( " /n " + customer.Name + " has been " + (eligible ? " Approved " : " Rejected " ));
30 Console.ReadLine();
31 }
32 }
可以看到,在不用Faade 模式的情況下,客戶程序與三個子系統都發生了耦合,這種耦合使得客戶程序依賴於子系統,當子系統 化時,客戶程序也將面臨很多變化的挑戰。一個合情合理的設計就是爲這些子系統創建一個統一的接口,這個接口簡化了客戶程序的判斷操作。看一下引入 Faade 模式後的類結構圖:
變
外觀類 Mortage 的實現如下:
1 / 外觀類
2 public class Mortgage
3 {
4 private Bank bank = new Bank();
5 private Loan loan = new Loan();
6 private Credit credit = new Credit();
7
8 public bool IsEligible(Customer cust, int amount)
9 {
10 Console.WriteLine( " {0} applies for {1:C} loan/n " ,
11 cust.Name, amount);
12
13 bool eligible = true ;
14
15 if ( ! bank.HasSufficientSavings(cust, amount))
16 {
17 eligible = false ;
18 }
19 else if ( ! loan.HasNoBadLoans(cust))
20 {
21 eligible = false ;
22 }
23 else if ( ! credit.HasGoodCredit(cust))
24 {
25 eligible = false ;
26 }
27
28 return eligible;
29 }
30 }
顧客類和子系統類的實現仍然如下:
1 // 銀行子系統
2 public class Bank
3 {
4 public bool HasSufficientSavings(Customer c, int amount)
5 {
6 Console.WriteLine( " Check bank for " + c.Name);
7 return true ;
8 }
9 }
10
11 // 信用證子系統
12 public class Credit
13 {
14 public bool HasGoodCredit(Customer c)
15 {
16 Console.WriteLine( " Check credit for " + c.Name);
17 return true ;
18 }
19 }
20
21 // 貸款子系統
22 public class Loan
23 {
24 public bool HasNoBadLoans(Customer c)
25 {
26 Console.WriteLine( " Check loans for " + c.Name);
27 return true ;
28 }
29 }
30
31 // 顧客類
32 public class Customer
33 {
34 private string name;
35
36 public Customer( string name)
37 {
38 this .name = name;
39 }
40
41 public string Name
42 {
43 get { return name; }
44 }
45 }
而此時客戶程序的實現:
1 // 客戶程序類
2 public class MainApp
3 {
4 public static void Main()
5 {
6 // 外觀
7 Mortgage mortgage = new Mortgage();
8
9 Customer customer = new Customer( " Ann McKinsey " );
10 bool eligable = mortgage.IsEligible(customer, 125000 );
11
12 Console.WriteLine( " /n " + customer.Name +
13 " has been " + (eligable ? " Approved " : " Rejected " ));
14 Console.ReadLine();
15 }
16 }
可以看到引入Faade 模式後,客戶程序只與 Mortgage 發生依賴,也就是 Mortgage 屏蔽了子系統之間的複雜的操作,達到了解耦內部子系統與客戶程序之間的依賴。
.NET 架構中的 Faade 模式
Faade 模式在實際開發中最多的運用當屬開發 N 層架構的應用程序了,一個典型的 N 層結構如下:
在這個架構中,總共分爲四個邏輯層,分別爲:用戶層 UI ,業務外觀層 Business Faade ,業務規則層 Business Rule ,數據訪問層 Data Access 。其中 Business Faade 層的職責如下:
l 從“用戶”層接收用戶輸入
l 如果請求需要對數據進行只讀訪問,則可能使用“數據訪問”層
l 將請求傳遞到“業務規則”層
l 將響應從“業務規則”層返回到“用戶”層
l 在對“業務規則”層的調用之間維護臨時狀態
對這一架構最好的體現就是 Duwamish 示例了。在該應用程序中,有部分操作只是簡單的從數據庫根據條件提取數據,不需要經過任何處理,而直接將數據顯示到網頁上,比如查詢某類別的圖書列表。而另外一些操作,比如計算定單中圖書的總價並根據顧客的級別計算回扣等等,這部分往往有許多不同的功能的類,操作起來也比較複雜。如果採用傳統的三層結構,這些商業邏輯一般是會放在中間層,那麼對內部的這些大量種類繁多,使用方法也各異的不同的類的調用任務,就完全落到了表示層。這樣勢必會增加表示層的代碼量,將表示層的任務複雜化,和表示層只負責接受用戶的輸入並返回結果的任務不太相稱,並增加了層與層之間的耦合程度。於是就引入了一個 Faade 層,讓這個 Facade 來負責管理系統內部類的調用,併爲表示層提供了一個單一 而簡單的接口。看一下Duwamish結構圖:
從圖中可以看到,UI層 將請求發送給業務外觀層,業務外觀層對請求進行初步的處理,判斷是否需要調用業務規則層,還是直接調用數據訪問層獲取數據。最後由數據訪問層訪問數據庫並按 照來時的步驟返回結果到 UI 層,來看具體的代碼實現。
在獲取商品目錄的時候, Web UI 調用業務外觀層:
1 productSystem = new ProductSystem();
2 categorySet = productSystem.GetCategories(categoryID);
業務外觀層直接調用了數據訪問層:
1 public CategoryData GetCategories( int categoryId)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(categoryId >= 0 , " Invalid Category Id " ,ApplicationAssert.LineNumber);
7 //
8 // Retrieve the data
9 //
10 using (Categories accessCategories = new Categories())
11 {
12 return accessCategories.GetCategories(categoryId);
13 }
14
15 }
在添加訂單時,UI調用業務外觀層:
1 public void AddOrder()
2 {
3 ApplicationAssert.CheckCondition(cartOrderData != null , " Order requires data " , ApplicationAssert.LineNumber);
4
5 // Write trace log.
6 ApplicationLog.WriteTrace( " Duwamish7.Web.Cart.AddOrder:/r/nCustomerId: " +
7 cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[ 0 ][OrderData.PKID_FIELD].ToString());
8 cartOrderData = ( new OrderSystem()).AddOrder(cartOrderData);
9 }
業務外觀層調用業務規則層:
1 public OrderData AddOrder(OrderData order)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(order != null , " Order is required " , ApplicationAssert.LineNumber);
7
8 ( new Busine***ules.Order()).InsertOrder(order);
9 return order;
10 }
業務規則層進行復雜的邏輯處理後,再調用數據訪問層:
1 public OrderData AddOrder(OrderData order)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(order != null , " Order is required " , ApplicationAssert.LineNumber);
7
8 ( new Busine***ules.Order()).InsertOrder(order);
9 return order;
10 }
11
12
13 業務規則層進行復雜的邏輯處理後,再調用數據訪問層:
14 public bool InsertOrder(OrderData order)
15 {
16 //
17 // Assume it's good
18 //
19 bool isValid = true ;
20 //
21 // Validate order summary
22 //
23 DataRow summaryRow = order.Tables[OrderData.ORDER_SUMMARY_TABLE].Rows[ 0 ];
24
25 summaryRow.ClearErrors();
26
27 if (CalculateShipping(order) != (Decimal)(summaryRow[OrderData.SHIPPING_HANDLING_FIELD]))
28 {
29 summaryRow.SetColumnError(OrderData.SHIPPING_HANDLING_FIELD, OrderData.INVALID_FIELD);
30 isValid = false ;
31 }
32
33 if (CalculateTax(order) != (Decimal)(summaryRow[OrderData.TAX_FIELD]))
34 {
35 summaryRow.SetColumnError(OrderData.TAX_FIELD, OrderData.INVALID_FIELD);
36 isValid = false ;
37 }
38 //
39 // Validate shipping info
40 //
41 isValid &= IsValidField(order, OrderData.SHIPPING_ADDRESS_TABLE, OrderData.SHIP_TO_NAME_FIELD, 40 );
42 //
43 // Validate payment info
44 //
45 DataRow paymentRow = order.Tables[OrderData.PAYMENT_TABLE].Rows[ 0 ];
46
47 paymentRow.ClearErrors();
48
49 isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_TYPE_FIELD, 40 );
50 isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_NUMBER_FIELD, 32 );
51 isValid &= IsValidField(paymentRow, OrderData.EXPIRATION_DATE_FIELD, 30 );
52 isValid &= IsValidField(paymentRow, OrderData.NAME_ON_CARD_FIELD, 40 );
53 isValid &= IsValidField(paymentRow, OrderData.BILLING_ADDRESS_FIELD, 255 );
54 //
55 // Validate the order items and recalculate the subtotal
56 //
57 DataRowCollection itemRows = order.Tables[OrderData.ORDER_ITEMS_TABLE].Rows;
58
59 Decimal subTotal = 0 ;
60
61 foreach (DataRow itemRow in itemRows)
62 {
63 itemRow.ClearErrors();
64
65 subTotal += (Decimal)(itemRow[OrderData.EXTENDED_FIELD]);
66
67 if ((Decimal)(itemRow[OrderData.PRICE_FIELD]) <= 0 )
68 {
69 itemRow.SetColumnError(OrderData.PRICE_FIELD, OrderData.INVALID_FIELD);
70 isValid = false ;
71 }
72
73 if (( short )(itemRow[OrderData.QUANTITY_FIELD]) <= 0 )
74 {
75 itemRow.SetColumnError(OrderData.QUANTITY_FIELD, OrderData.INVALID_FIELD);
76 isValid = false ;
77 }
78 }
79 //
80 // Verify the subtotal
81 //
82 if (subTotal != (Decimal)(summaryRow[OrderData.SUB_TOTAL_FIELD]))
83 {
84 summaryRow.SetColumnError(OrderData.SUB_TOTAL_FIELD, OrderData.INVALID_FIELD);
85 isValid = false ;
86 }
87
88 if ( isValid )
89 {
90 using (DataAccess.Orders ordersDataAccess = new DataAccess.Orders())
91 {
92 return (ordersDataAccess.InsertOrderDetail(order)) > 0 ;
93 }
94 }
95 else
96 return false ;
97 }
Facade模式的個要點:
從客戶程序的角度來看,Facade模式不僅簡化了整個組件系統的接口,同時對於組件內部與外部客戶程序來說,從某種程度上也達到了一種“解耦”的效果----內部子系統的任何變化不會影響到Facade接口的變化。
Facade設計模式更注重從架構的層次去看整個系統,而不是單個類的層次。Facdae很多時候更是一種架構
設計模式。
注意區分Facade模式、Adapter模式、Bridge模式與Decorator模式。Facade模式注重簡化接口,Adapter模式注重轉換接口,Bridge模式注重分離接口(抽象)與其實現,Decorator模式注重穩定接口的前提下爲對象擴展功能。