在PetShop 4.0中ASP.NET緩衝的實現(2)

來源:互聯網
上載者:User

4.3.4  引入Facade模式

利用Facade模式可以將一些複雜的邏輯進行封裝,以方便調用者對這些複雜邏輯的調用。就好像提供一個統一的門面一般,將內部的子系統封裝起來,統一為一個高層次的介面。一個典型的Facade模式如下所示:


圖4-4 Facade模式

Facade 模式的目的並非要引入一個新的功能,而是在現有功能的基礎上提供一個更高層次的抽象,使得調用者可以直接調用,而不用關心內部的實現方式。以 CacheDependency工廠為例,我們需要為調用者提供獲得AggregateCacheDependency對象的簡便方法,因而建立了 DependencyFacade類:
public static class DependencyFacade
{
    private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
    public static AggregateCacheDependency GetCategoryDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateCategoryDependency().GetDependency();
        else
            return null;
    }
    public static AggregateCacheDependency GetProductDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateProductDependency().GetDependency();
        else
            return null;
        }
    public static AggregateCacheDependency GetItemDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateItemDependency().GetDependency();
        else
            return null;
    }
}

DependencyFacade類封裝了擷取AggregateCacheDependency類型對象的邏輯,如此一來,調用者可以調用相關方法獲得建立相關依賴項的AggregateCacheDependency類型對象:
AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

比起直接調用DependencyAccess類的GetDependency()方法而言,除了方法更簡單之外,同時它還對CacheDependencyAssembly配置節進行了判斷,如果其值為空白,則返回null對象。

在PetShop.Web的App_Code檔案夾下,靜態類WebUtility的GetCategoryName()和GetProductName()方法調用了DependencyFacade類。例如GetCategoryName()方法:
public static string GetCategoryName(string categoryId)
{
     Category category = new Category();
     if (!enableCaching)
            return category.GetCategory(categoryId).Name;

     string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

     // 檢查緩衝中是否存在該資料項目;
     string data = (string)HttpRuntime.Cache[cacheKey];
     if (data == null)
     {
           // 通過web.config的配置擷取duration值;
           int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
           // 如果緩衝中不存在該資料項目,則通過商務邏輯層訪問資料庫擷取;
           data = category.GetCategory(categoryId).Name;
           // 通過Facade類建立AggregateCacheDependency對象;
           AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
           // 將資料項目以及AggregateCacheDependency Object Storage Service到緩衝中;
           HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
      }
      return data;
}

GetCategoryName ()方法首先會檢查緩衝中是否已經存在CategoryName資料項目,如果已經存在,就通過緩衝直接擷取資料;否則將通過商務邏輯層調用資料訪問層訪問資料庫獲得CategoryName,在獲得了CategoryName後,會將新擷取的資料連同DependencyFacade類建立的 AggregateCacheDependency對象添加到緩衝中。

WebUtility靜態類被展示層的許多頁面所調用,例如Product頁面:
public partial class Products : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
    }
}

顯示頁面title的邏輯是放在Page_Load事件方法中,因而每次開啟該頁面都要執行擷取CategoryName的方法。如果沒有採用緩衝機制,當Category資料較多時,頁面的顯示就會非常緩慢。

4.3.5  引入Proxy模式

商務邏輯層BLL中與Product、Category、Item有關的業務方法,其實現邏輯是調用資料訪問層(DAL)對象訪問資料庫,以擷取相關資料。為了改善系統效能,我們就需要為這些實現方法增加緩衝機制的邏輯。當我們操作增加了緩衝機制的業務對象時,對於調用者而言,應與BLL業務對象的調用保持一致。也即是說,我們需要引入一個新的對象去控制原來的BLL業務對象,這個新的對象就是Proxy模式中的代理對象。

以PetShop.BLL.Product業務對象為例,PetShop為其建立了代理對象ProductDataProxy,並在GetProductByCategory()等方法中,引入了緩衝機制,例如:
public static class ProductDataProxy
{

    private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
    private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
       
    public static IList
GetProductsByCategory(string category)
    {
        Product product = new Product();

        if (!enableCaching)
            return product.GetProductsByCategory(category);

        string key = "product_by_category_" + category;
        IList data = (IList )HttpRuntime.Cache[key];

        // Check if the data exists in the data cache
        if (data == null)
        {
            data = product.GetProductsByCategory(category);

            // Create a AggregateCacheDependency object from the factory
            AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

            // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
            HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }
        return data;
    }
}

與商務邏輯層Product對象的GetProductsByCategory()方法相比,增加了緩衝機制。當緩衝內不存在相關資料項目時,則直接調用商務邏輯層Product的GetProductsByCategory()方法來擷取資料,並將其與對應的 AggregateCacheDependency對象一起儲存在緩衝中。

引入Proxy模式,實現了在緩衝層級上對業務對象的封裝,增強了對業務對象的控制。由於暴露在對象外的方法是一致的,因而對於調用方而言,調用代理對象與真實對象並沒有實質的區別。

從職責分離與分層設計的角度分析,我更希望這些Proxy對象是被定義在商務邏輯層中,而不像在PetShop的設計那樣,被劃分到展示層UI中。此外,如果需要考慮程式的可擴充性與可替換性,我們還可以為真實對象與代理對象建立統一的介面或抽象類別。然而,單以PetShop的展示層調用來看,採用靜態類與靜態方法的方式,或許更為合理。我們需要謹記,“過度設計”是軟體設計的警戒線。

如果需要對UI層採用緩衝機制,將應用程式資料存放到緩衝中,就可以調用這些代理對象。以ProductsControl使用者控制項為例,調用方式如下:
productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

productsList對象屬於自訂的CustomList類型,這是一個派生自System.Web.UI.WebControls.DataList控制項的類,它的DataSource屬性可以接受IList集合對象。
不過在PetShop 4.0的設計中,對於類似於ProductsControl類型的控制項而言,採用的緩衝機制是頁輸出緩衝。我們可以從ProductsControl.ascx頁面的Source代碼中發現端倪:
<%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

與ASP.NET 1.x的頁輸出緩衝不同的是,在ASP.NET 2.0中,為ASP.NET使用者控制項新引入了CachePolicy屬性,該屬性的類型為ControlCachePolicy類,它以編程方式實現了對 ASP.NET使用者控制項的輸出緩衝設定。我們可以通過設定ControlCachePolicy類的Dependency屬性,來設定與該使用者控制項相關的依賴項,例如在ProductsControl使用者控制項中,進行如下的設定:
protected void Page_Load(object sender, EventArgs e)
{
    this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
}

採用頁輸出緩衝,並且利用ControlCachePolicy設定輸出緩衝,能夠將業務資料與整個頁面放入到緩衝中。這種方式比起應用程式緩衝而言,在效能上有很大的提高。同時,它又通過引入的SqlCacheDependency特性有效地避免了“資料到期”的缺點,因而在PetShop 4.0中被廣泛採用。相反,之前為Product、Category、Item業務對象建立的代理對象則被“投閑散置”,僅僅作為一種設計方法的展示而 “倖存”與整個系統的原始碼中。

<img onload="if(this.width>515)this.width=515" src="file:///C:/DOCUME%7E1/zhangl/LOCALS%7E1/Temp/moz-screenshot.jpg" alt="">PetShop作為一個B2C的寵物網上商店,需要充分考慮訪客的使用者體驗,如果因為資料量大而導致Web伺服器的響應不及時,頁面和查詢資料遲遲得不到結果,會因此而破壞客戶訪問網站的心情,在耗盡耐心的等待後,可能會失去這一部分客戶。無疑,這是非常糟糕的結果。因而在對其進行體系架構設計時,整個系統的效能就顯得殊為重要。然而,我們不能因噎廢食,因為專註於效能而忽略資料的正確性。在PetShop 3.0版本以及之前的版本,因為ASP.NET緩衝的局限性,這一問題並沒有得到很好的解決。PetShop 4.0則引入了SqlCacheDependency特性,使得系統對緩衝的處理較之以前大為改觀。

4.3.1  CacheDependency介面

PetShop 4.0引入了SqlCacheDependency特性,對Category、Product和Item資料表對應的緩衝實施了SQL Cache Invalidation技術。當對應的資料表資料發生更改後,該技術能夠將相關項目從緩衝中移除。實現這一技術的核心是 SqlCacheDependency類,它繼承了CacheDependency類。然而為了保證整個架構的可擴充性,我們也允許設計者建立自訂的 CacheDependency類,用以擴充緩衝依賴。這就有必要為CacheDependency建立抽象介面,並在web.config檔案中進行配置。

在PetShop 4.0的命名空間PetShop.ICacheDependency中,定義了名為IPetShopCacheDependency介面,它僅包含了一個介面方法:
public interface IPetShopCacheDependency
{      
    AggregateCacheDependency GetDependency();
}

AggregateCacheDependency是.Net Framework 2.0新增的一個類,它負責監視依賴項對象的集合。當這個集合中的任意一個依賴項對象發生改變時,該依賴項對象對應的緩衝對象都將被自動移除。
AggregateCacheDependency 類起到了組合CacheDependency對象的作用,它可以將多個CacheDependency對象甚至於不同類型的 CacheDependency對象與快取項目建立關聯。由於PetShop需要為Category、Product和Item資料表建立依賴項,因而 IPetShopCacheDependency的介面方法GetDependency()其目的就是返回建立了這些依賴項的 AggregateCacheDependency對象。

4.3.2  CacheDependency實現

CacheDependency的實現正是為Category、Product和Item資料表建立了對應的SqlCacheDependency類型的依賴項,如代碼所示:
public abstract class TableDependency : IPetShopCacheDependency
{
    // This is the separator that's used in web.config
    protected char[] configurationSeparator = new char[] { ',' };

    protected AggregateCacheDependency dependency = new AggregateCacheDependency();
    protected TableDependency(string configKey)
    {
        string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
        string tableConfig = ConfigurationManager.AppSettings[configKey];
        string[] tables = tableConfig.Split(configurationSeparator);

        foreach (string tableName in tables)
            dependency.Add(new SqlCacheDependency(dbName, tableName));
    }
    public AggregateCacheDependency GetDependency()
   {
        return dependency;
    }
}

需要建立依賴項的資料庫與資料表都配置在web.config檔案中,其設定如下:

根據各個資料表間的依賴關係,因而不同的資料表需要建立的依賴項也是不相同的,從設定檔中的value值可以看出。然而不管建立依賴項的多寡,其建立的行為邏輯都是相似的,因而在設計時,抽象了一個共同的類TableDependency,並通過建立帶參數的建構函式,完成對依賴項的建立。由於介面方法 GetDependency()的實現中,返回的對象dependency是在受保護的建構函式建立的,因此這裡的實現方式也可以看作是Template Method模式的靈活運用。例如TableDependency的子類Product,就是利用父類的建構函式建立了Product、Category 資料表的SqlCacheDependency依賴:
public class Product : TableDependency
{
    public Product() : base("ProductTableDependency") { }
}

如果需要自訂CacheDependency,那麼建立依賴項的方式又有不同。然而不管是建立SqlCacheDependency對象,還是自訂的 CacheDependency對象,都是將這些依賴項添加到AggregateCacheDependency類中,因而我們也可以為自訂 CacheDependency建立專門的類,只要實現IPetShopCacheDependency介面即可。

4.3.3  CacheDependency工廠

繼承了抽象類別TableDependency的Product、Category和Item類均需要在調用時建立各自的對象。由於它們的父類 TableDependency實現了介面IPetShopCacheDependency,因而它們也間接實現了 IPetShopCacheDependency介面,這為實現原廠模式提供了前提。

在PetShop 4.0中,依然利用了設定檔和反射技術來實現原廠模式。命名空間PetShop.CacheDependencyFactory中,類DependencyAccess即為建立IPetShopCacheDependency對象的工廠類:
public static class DependencyAccess
{       
    public static IPetShopCacheDependency CreateCategoryDependency()
    {
        return LoadInstance("Category");
    }
    public static IPetShopCacheDependency CreateProductDependency()
    {
        return LoadInstance("Product");
    }
    public static IPetShopCacheDependency CreateItemDependency()
    {
        return LoadInstance("Item");
    }
    private static IPetShopCacheDependency LoadInstance(string className)
    {
        string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
        string fullyQualifiedClass = path + "." + className;
        return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
    }
}
整個原廠模式的實現4-3所示:

C:\Documents and Settings\zhangl\案頭\20061102063110194.gif
 圖4-3 CacheDependency工廠

雖然DependencyAccess類建立了實現了IPetShopCacheDependency介面的類Category、Product、 Item,然而我們之所以引入IPetShopCacheDependency介面,其目的就在於獲得建立了依賴項的 AggregateCacheDependency類型的對象。我們可以調用對象的介面方法GetDependency(),如下所示:
AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();

為了方便調用者,似乎我們可以對DependencyAccess類進行改進,將原有的CreateCategoryDependency()方法,修改為建立AggregateCacheDependency類型對象的方法。

然而這樣的做法擾亂了作為工廠類的DependencyAccess的本身職責,且建立IPetShopCacheDependency介面對象的行為仍然有可能被調用者調用,所以保留原有的DependencyAccess類仍然是有必要的。

在PetShop 4.0的設計中,是通過引入Facade模式以方便調用者更加簡單地獲得AggregateCacheDependency類型對象。

4.3.4  引入Facade模式

利用Facade模式可以將一些複雜的邏輯進行封裝,以方便調用者對這些複雜邏輯的調用。就好像提供一個統一的門面一般,將內部的子系統封裝起來,統一為一個高層次的介面。一個典型的Facade模式如下所示:

圖4-4 Facade模式

Facade 模式的目的並非要引入一個新的功能,而是在現有功能的基礎上提供一個更高層次的抽象,使得調用者可以直接調用,而不用關心內部的實現方式。以 CacheDependency工廠為例,我們需要為調用者提供獲得AggregateCacheDependency對象的簡便方法,因而建立了 DependencyFacade類:
public static class DependencyFacade
{
    private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
    public static AggregateCacheDependency GetCategoryDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateCategoryDependency().GetDependency();
        else
            return null;
    }
    public static AggregateCacheDependency GetProductDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateProductDependency().GetDependency();
        else
            return null;
        }
    public static AggregateCacheDependency GetItemDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateItemDependency().GetDependency();
        else
            return null;
    }
}

DependencyFacade類封裝了擷取AggregateCacheDependency類型對象的邏輯,如此一來,調用者可以調用相關方法獲得建立相關依賴項的AggregateCacheDependency類型對象:
AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

比起直接調用DependencyAccess類的GetDependency()方法而言,除了方法更簡單之外,同時它還對CacheDependencyAssembly配置節進行了判斷,如果其值為空白,則返回null對象。

在PetShop.Web的App_Code檔案夾下,靜態類WebUtility的GetCategoryName()和GetProductName()方法調用了DependencyFacade類。例如GetCategoryName()方法:
public static string GetCategoryName(string categoryId)
{
     Category category = new Category();
     if (!enableCaching)
            return category.GetCategory(categoryId).Name;

     string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

     // 檢查緩衝中是否存在該資料項目;
     string data = (string)HttpRuntime.Cache[cacheKey];
     if (data == null)
     {
           // 通過web.config的配置擷取duration值;
           int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
           // 如果緩衝中不存在該資料項目,則通過商務邏輯層訪問資料庫擷取;
           data = category.GetCategory(categoryId).Name;
           // 通過Facade類建立AggregateCacheDependency對象;
           AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
           // 將資料項目以及AggregateCacheDependency Object Storage Service到緩衝中;
           HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
      }
      return data;
}

GetCategoryName ()方法首先會檢查緩衝中是否已經存在CategoryName資料項目,如果已經存在,就通過緩衝直接擷取資料;否則將通過商務邏輯層調用資料訪問層訪問資料庫獲得CategoryName,在獲得了CategoryName後,會將新擷取的資料連同DependencyFacade類建立的 AggregateCacheDependency對象添加到緩衝中。

WebUtility靜態類被展示層的許多頁面所調用,例如Product頁面:
public partial class Products : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
    }
}

顯示頁面title的邏輯是放在Page_Load事件方法中,因而每次開啟該頁面都要執行擷取CategoryName的方法。如果沒有採用緩衝機制,當Category資料較多時,頁面的顯示就會非常緩慢。

4.3.5  引入Proxy模式

商務邏輯層BLL中與Product、Category、Item有關的業務方法,其實現邏輯是調用資料訪問層(DAL)對象訪問資料庫,以擷取相關資料。為了改善系統效能,我們就需要為這些實現方法增加緩衝機制的邏輯。當我們操作增加了緩衝機制的業務對象時,對於調用者而言,應與BLL業務對象的調用保持一致。也即是說,我們需要引入一個新的對象去控制原來的BLL業務對象,這個新的對象就是Proxy模式中的代理對象。

以PetShop.BLL.Product業務對象為例,PetShop為其建立了代理對象ProductDataProxy,並在GetProductByCategory()等方法中,引入了緩衝機制,例如:
public static class ProductDataProxy
{

    private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
    private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
       
    public static IList
GetProductsByCategory(string category)
    {
        Product product = new Product();

        if (!enableCaching)
            return product.GetProductsByCategory(category);

        string key = "product_by_category_" + category;
        IList data = (IList )HttpRuntime.Cache[key];

        // Check if the data exists in the data cache
        if (data == null)
        {
            data = product.GetProductsByCategory(category);

            // Create a AggregateCacheDependency object from the factory
            AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

            // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
            HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }
        return data;
    }
}

與商務邏輯層Product對象的GetProductsByCategory()方法相比,增加了緩衝機制。當緩衝內不存在相關資料項目時,則直接調用商務邏輯層Product的GetProductsByCategory()方法來擷取資料,並將其與對應的 AggregateCacheDependency對象一起儲存在緩衝中。

引入Proxy模式,實現了在緩衝層級上對業務對象的封裝,增強了對業務對象的控制。由於暴露在對象外的方法是一致的,因而對於調用方而言,調用代理對象與真實對象並沒有實質的區別。

從職責分離與分層設計的角度分析,我更希望這些Proxy對象是被定義在商務邏輯層中,而不像在PetShop的設計那樣,被劃分到展示層UI中。此外,如果需要考慮程式的可擴充性與可替換性,我們還可以為真實對象與代理對象建立統一的介面或抽象類別。然而,單以PetShop的展示層調用來看,採用靜態類與靜態方法的方式,或許更為合理。我們需要謹記,“過度設計”是軟體設計的警戒線。

如果需要對UI層採用緩衝機制,將應用程式資料存放到緩衝中,就可以調用這些代理對象。以ProductsControl使用者控制項為例,調用方式如下:
productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

productsList對象屬於自訂的CustomList類型,這是一個派生自System.Web.UI.WebControls.DataList控制項的類,它的DataSource屬性可以接受IList集合對象。
不過在PetShop 4.0的設計中,對於類似於ProductsControl類型的控制項而言,採用的緩衝機制是頁輸出緩衝。我們可以從ProductsControl.ascx頁面的Source代碼中發現端倪:
&lt;%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %&gt;

與ASP.NET 1.x的頁輸出緩衝不同的是,在ASP.NET 2.0中,為ASP.NET使用者控制項新引入了CachePolicy屬性,該屬性的類型為ControlCachePolicy類,它以編程方式實現了對 ASP.NET使用者控制項的輸出緩衝設定。我們可以通過設定ControlCachePolicy類的Dependency屬性,來設定與該使用者控制項相關的依賴項,例如在ProductsControl使用者控制項中,進行如下的設定:
protected void Page_Load(object sender, EventArgs e)
{
    this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
}

採用頁輸出緩衝,並且利用ControlCachePolicy設定輸出緩衝,能夠將業務資料與整個頁面放入到緩衝中。這種方式比起應用程式緩衝而言,在效能上有很大的提高。同時,它又通過引入的SqlCacheDependency特性有效地避免了“資料到期”的缺點,因而在PetShop 4.0中被廣泛採用。相反,之前為Product、Category、Item業務對象建立的代理對象則被“投閑散置”,僅僅作為一種設計方法的展示而 “倖存”與整個系統的原始碼中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.