在.Net PetShop 4.0中,非常成功地使用了工厂模式以及接口(interface)、静态类(Static class)、抽象类(abstract class)等成员。在使用缓存时,也是通过web.config配置进行设置,在使用时非常灵活。下面从底向上具体分析.Net PetShop 4.0缓存方面的技术。
首先看一下该项目中与缓存直接相关的命名空间:
PetShop.ICacheDependency
PetShop.TableCacheDependency
PetShop.CacheDependencyFactory
PetShop.Web
一、PetShop.ICacheDependency命名空间
最低层应该是接口的定义了,在PetShop.ICacheDependency命名空间中只定义了一个接口 IPetShopCacheDependency,该接口只有一个方法 GetDependency,没有任何参数,返回AggregateCacheDependency类型。 AggregateCacheDependency是在.NET Framework 2.0 版中是新增的类,组合 ASP.NET 应用程序的 Cache 对象中存储的项和 CacheDependency 对象的数组之间的多个依赖项(MSDN中原话)。
二、PetShop.TableCacheDependency命名空间
在PetShop.TableCacheDependency命名空间中,提供两种类:抽象类TableDependency和它的继承类Category、Item和Product。抽象类TableDependency的构造函数为:
1 protected TableDependency(string configKey) {
2
3 string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
4 string tableConfig = ConfigurationManager.AppSettings[configKey];
5 string[] tables = tableConfig.Split(configurationSeparator);
6
7 foreach (string tableName in tables)
8 dependency.Add(new SqlCacheDependency(dbName, tableName));
9 }
10
传递了一个参数configKey,根据该参数从web.config文件中获取表名列表,同时在web.config中获取数据库名称。将表名列表中的所有数据表添加到AggregateCacheDependency类型的dependency变量中。在此外使用了.NET Framework 2.0 版中是新增的另一个与缓存有关的SqlCacheDependency类。这个类用于建立ASP.NET应用程序的Cache对象中存储的项和特定SQL Server数据库表之间的联系。AggregateCacheDependency和SqlCacheDependency都从 CacheDependency继承而来,但在.NET 2.0中还未提供Oracle等其它数据库对应的类。
下面是web.config文件中与缓存相关的设置:
1 <!-- Cache dependency options. Possible values: PetShop.TableCacheDependency for SQL Server and keep empty for ORACLE -->
2 <add key="CacheDependencyAssembly" value="PetShop.TableCacheDependency"/>
3 <!-- CacheDatabaseName should match the name under caching section, when using TableCacheDependency -->
4 <add key="CacheDatabaseName" value="MSPetShop4"/>
5 <!-- *TableDependency lists table dependency for each instance separated by comma -->
6 <add key="CategoryTableDependency" value="Category"/>
7 <add key="ProductTableDependency" value="Product,Category"/>
8 <add key="ItemTableDependency" value="Product,Category,Item"/>
9
每个继承类都只有一个构造函数,通过设置基类的configKey参数变成了三个不同的类。Product类的构造函数为:
public Product() : base("ProductTableDependency") { }
三 CacheDependency工厂
继承了抽象类TableDependency的Product、Category和Item类均需要在调用时创建各自的对象。由于它们的父类 TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了 IPetShopCacheDependency接口,这为实现工厂模式提供了前提。
在PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:
1 public static class DependencyAccess
2 {
3 public static IPetShopCacheDependency CreateCategoryDependency()
4 {
5 return LoadInstance("Category");
6 }
7 public static IPetShopCacheDependency CreateProductDependency()
8 {
9 return LoadInstance("Product");
10 }
11 public static IPetShopCacheDependency CreateItemDependency()
12 {
13 return LoadInstance("Item");
14 }
15 private static IPetShopCacheDependency LoadInstance(string className)
16 {
17 string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
18 string fullyQualifiedClass = path + "." + className;
19 return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
20 }
21 }
在这个方法中通过配置文件中的设置和传进来的参数className,返回相对应的程序集和类。DependencyAccess类里面的其它三个方法,只是调用这个方法,传入不同的参数而已。
整个工厂模式的实现如图4-3所示:
DependencyFacade 类提供的三个方法正好与DependencyAccess类的三个方法相对应,分别获取Category、Item和Product的 AggregateCacheDependency。在DependencyFacade类中还读取了web.config中的 CacheDependencyAssembly设置,从而决定是调用DependencyAccess对应的方法,还是直接返回null。
1 public static class DependencyFacade
2 {
3 private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
4 public static AggregateCacheDependency GetCategoryDependency()
5 {
6 if (!string.IsNullOrEmpty(path))
7 return DependencyAccess.CreateCategoryDependency().GetDependency();
8 else
9 return null;
10 }
11 public static AggregateCacheDependency GetProductDependency()
12 {
13 if (!string.IsNullOrEmpty(path))
14 return DependencyAccess.CreateProductDependency().GetDependency();
15 else
16 return null;
17 }
18 public static AggregateCacheDependency GetItemDependency()
19 {
20 if (!string.IsNullOrEmpty(path))
21 return DependencyAccess.CreateItemDependency().GetDependency();
22 else
23 return null;
24 }
25 }
26
DependencyFacade类封装了获取AggregateCacheDependency类型对象的逻辑,如此一来,调用者可以调用相关方法获得创建相关依赖项的AggregateCacheDependency类型对象:
1 AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();
比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象。
虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类Category、Product、 Item,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的 AggregateCacheDependency类型的对象。
四、PetShop.Web命名空间
在PetShop.Web 的App_Code中,有四个静态类与缓存直接相关,分别是CategoryDataProxy、ItemDataProxy、 ProductDataProxy和WebUtility。其中前三个分别调用DependencyFacade对应的方法。例如GetCategoryName()方法:
1 public static string GetCategoryName(string categoryId)
2 {
3 Category category = new Category();
4 if (!enableCaching)
5 return category.GetCategory(categoryId).Name;
6 string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);
7 // 检查缓存中是否存在该数据项;
8 string data = (string)HttpRuntime.Cache[cacheKey];
9 if (data == null)
10 {
11 // 通过web.config的配置获取duration值;
12 int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
13 // 如果缓存中不存在该数据项,则通过业务逻辑层访问数据库获取;
14 data = category.GetCategory(categoryId).Name;
15 // 通过Facade类创建AggregateCacheDependency对象;
16 AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
17 // 将数据项以及AggregateCacheDependency 对象存储到缓存中;
18 HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
19 }
20 return data;
21 }
22
GetCategoryName ()方法首先会检查缓存中是否已经存在CategoryName数据项,如果已经存在,就通过缓存直接获取数据;否则将通过业务逻辑层调用数据访问层访问数据库获得CategoryName,在获得了CategoryName后,会将新获取的数据连同DependencyFacade类创建的 AggregateCacheDependency对象添加到缓存中。
WebUtility静态类被表示层的许多页面所调用,例如Product页面:
1 public partial class Products : System.Web.UI.Page
2 {
3 protected void Page_Load(object sender, EventArgs e)
4 {
5 Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
6 }
7 }