程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET設計模式(12):外觀模式

.NET設計模式(12):外觀模式

編輯:關於.NET

概述

在軟件開發系統中,客戶程序經常會與復雜系統的內部子系統之間產生耦合,而導致客戶程序隨著子系統的變化而變化。那麼如何簡化客戶程序與子系統之間的交互接口?如何將復雜系統的內部子系統與客戶程序之間的依賴解耦?這就是要說的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)
{
  //
  // 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;
}
[MSDN]

效果及實現要點

1.Faç ;ade模式對客戶屏蔽了子系統組件,因而減少了客戶處理的對象的數目並使得子系統使用起來更加方便。

2.Faç ;ade模式實現了子系統與客戶之間的松耦合關系,而子系統內部的功能組件往往是緊耦合的。松耦合關系使得子系統的組件變化不會影響到它的客戶。

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

適用性

1.為一個復雜子系統提供一個簡單接口。

2.提高子系統的獨立性。

3.在層次化結構中,可以使用Facade模式定義系統中每一層的入口。

總結

Faç ;ade模式注重的是簡化接口,它更多的時候是從架構的層次去看整個系統,而並非單個類的層次。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved