petshop4.0缓存思想解析

首先是接口:所有得到相对应缓存的类都要实现的方法
public interface IPetShopCacheDependency
{
      AggregateCacheDependency GetDependency();//得到一个缓冲集合
}

 

需要建立依赖项的数据库与数据表都配置在web.config文件中,其设置如下:都是键值对,且都是写死在配置文件里的

<add key="CacheDatabaseName" value="MSPetShop4"/> //数据库名称
<add key="CategoryTableDependency" value="Category"/>
<add key="ProductTableDependency" value="Product,Category"/>
<add key="ItemTableDependency" value="Product,Category,Item"/>

表的缓存依赖是一个,其实这个抽象类的作用是设置缓存,或者说是【创建缓存】,然后又提供一个访问的接口给它的子类,这个接口即GetDependency(),由于这是抽象类,且它继承了接口,那么继承了这个类的子类也间接的继承了这个接口,但是子类可以不实现这个接口。但是接口就相当于子类的父类的父类,我们有一种说法是"小的可以指向大的,但是大的不能指向小的",就是当小的指向大的是很,小的值就为大的继承了小的值的那部分值(即大里面的小的)
public abstract class TableDependency : PetShop.ICacheDependency.IPetShopCacheDependency
{
        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;
        }
}


configKey为传进的参数,如:
public class Product : TableDependency
{
   public Product() : base("ProductTableDependency") //每个子类的base()中的参数是写死的,因为上面的配置文件是写死的,所以子类在实例化的时候不用管这个base即父类的构造函数的参数,因为它是写死的
   { }
}
上面产品类继承了这个抽象类之后就也实例化这个抽象类,用的参数是ProductTableDependency,依照上面的web.config文件配置,有键值对得<add key="ProductTableDependency" value="Product,Category"/>得到string[] tables={"Product","Category"},,,即经过foreach后,dependency得到的是
new SqlCacheDependency(“Product”,”MSPetShop4”)及new SqlCacheDependency(“Category”,”MSPetShop4”),这两个对象组成的AggregateCacheDependency集合


从上面看,对于不同的表对应的数据项的创建行为逻辑都是相似的,因而在设计时,抽象了一个共同的类TableDependency,

上面的:
public class Product : TableDependency
{
   public Product() : base("ProductTableDependency")
   { }
}

我们指针有一种说法是"小的可以指向大的,但是大的不能指向小的",就是当小的指向大的是很,小的值就为大的继承了小的值的那部分(即大里面的小的)

Assembly.Load(path).CreateInstance(fullyQualifiedClass);意思是根据配置文件和反射得到一个实例如 Product类实例,我们称这个是大的,因为 Product继承了抽象类,抽象类又继承了接口,所以我们称接口为小的,当小的指向大的时候,那个小的就为大的继承了小的那个的部分值(即大里面的小的)


所以我们在这里定义一个工厂叫CacheDependency工厂,是一个实例化每个类的工厂,但是这些类都被转换为只有接口了,即大的都被转换为小的了,即【这些类只有那个接口方法】了,其他什么都没有了,而这个接口方法的作用就是获取在父类中创建的缓存


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);
    }
}
由于上面根据名称得到的实例对象只有一个方法,即GetDependency(),得到的是这个类对应的缓存对象,返回类型为AggregateCacheDependency。
AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();


其实上面的接口,抽象类,子类的功能大概为这个意思:
    首先抽象类继承了接口,然后抽象类通过子类传进来的参数,然后配合配置文件创建每个类对应的缓存对象,保存在一个AggregateCacheDependency的集合中,然后又向外提供一个访问这个缓存对象集合的方法GetDependency。
    第二:子类继承了抽象类,在子类中只有一个工作就是顺便实例化这个子类相对应的父类,参数是写死的,而实例化这个父类的构造函数的同时也就意味着实例化了这个父类即在这个类的父类中创建了这个类的缓存对象
    第三,定义一个工厂DependencyAccess根据配置文件和反射通过传进的每个类的名称再次实例化每个子类,只是这个时候对子类做了个小动作,把这个子类转换为接口引用,那么这个子类就只能实现接口的方法,即获取缓存对象的方法。原理是小指向大的,那么小的就为大中的小的,理解指针的概念哦


然后上面的 DependencyAccess通过DependencyAccess.CreateCategoryDependency(),意思是创建某某缓存依赖,但是得到的却是接口类型对象,而不是我们要的AggregateCacheDependency,【然而这样的做法扰乱了作为工厂类的DependencyAccess的本身职责,且创建IPetShopCacheDependency接口对象的行为仍然有可能被调用者调用,所以保留原有的DependencyAccess类仍然是有必要的。】于是我们把类似这样的语句:
public static IPetShopCacheDependency CreateProductDependency()
   {
     return LoadInstance("Product");//得到的是接口类型的对象,而不是我们要的gregateCacheDependency集合
   }
于是我们再调用上面的代码再进行封装,而不是替代上面的,得到这个工厂类如方法名一样的东西即缓存对象集合AggregateCacheDependency

于是又了DependencyFacade模式,利用外观者模式
private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
public static AggregateCacheDependency GetCategoryDependency()//这样就名副其实的得到了AggregateCacheDependency集合
   {
     if (!string.IsNullOrEmpty(path))
        return DependencyAccess.CreateCategoryDependency().GetDependency();
     else  
        return null;
   }
配置字节为:<add key="CacheDependencyAssembly" value="PetShop.TableCacheDependency"/>

DependencyFacade类封装了获取AggregateCacheDependency类型对象的逻辑(即上面的DependencyAccess原理),如此一来,调用者可以调用相关方法获得创建相关依赖项的AggregateCacheDependency类型对象:
 AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();
比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象


再次总结:

其实上面的接口,抽象类,子类,DependencyAccess,DependencyFacade的功能大概为这个意思:
    首先抽象类继承了接口,然后抽象类通过子类传进来的参数,然后配合配置文件创建每个类对应的缓存对象,保存在一个AggregateCacheDependency的集合中,然后又向外提供一个访问这个缓存对象集合的方法GetDependency。
    第二:子类继承了抽象类,在子类中只有一个工作就是顺便实例化这个子类相对应的父类,参数是写死的,而实例化这个父类的构造函数的同时也就意味着实例化了这个父类即在这个类的父类中创建了这个类的缓存对象
    第三,定义一个工厂DependencyAccess根据配置文件和反射通过传进的每个类的名称再次实例化每个子类,只是这个时候对子类做了个小动作,把这个子类转换为接口引用,那么这个子类就只能实现接口的方法,即获取缓存对象的方法。原理是小指向大的,那么小的就为大中的小的,理解指针的概念哦(这个类中的方法目的是只能得到每个接口类型的引用,要得到缓存的话还要调用接口类型引用的GetDependency()方法)
    第四,上面的DependencyAccess通过public static IPetShopCacheDependency CreateItemDependency()方法只能得到接口对象的引用个,而不是如方法名那样得到缓存,所以我们定义DependencyFacade来通过封装上面的DependencyAcces定义一个名副其实的得到缓存的方法,如public static AggregateCacheDependency GetCategoryDependency()原理是通过调用上面的DependencyAccess中的每个接口对象引用的GetDependency()得到我们要的缓存集合
return DependencyAccess.CreateCategoryDependency().GetDependency(); 所以第四步只是对第三步的封装,得到如方法名那样的缓存对象而已,其他没什么了。


接下来是通过某个实体类对象的id获取他在数据库中表的name,然把id和某个字符串进行组合,形成cacheKey=string.Format(CATEGORY_NAME_KEY, categoryId);然后把这个cachekey作为像数组的下标一样,作为缓存的键,把取得的data = category.GetCategory(categoryId).Name;作为值,即缓存项,AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();把取得的值作为缓存依赖项,用cache添加到缓存中,HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null)

cache:缓存键,即数组下标
data:缓存项,即数组的值
dc:缓存依赖项,即该缓存项所对应的缓存集合,如果这个缓存依赖项做了变化就把缓存项删除,这样缓存项就为空,而该缓存键多对应的缓存项即空,当系统要获取信息时先判断下缓存键所对应的缓存项为空就重新到数据访问层去获取数据,重新填充缓存


在PetShop.Web的App_Code文件夹下,静态类WebUtility的GetCategoryName()和GetProductName()方法调用了DependencyFacade类。例如GetCategoryName()方法:
public static string GetCategoryName(string categoryId)
{ //categoryId:BIRDS BUGS BYARD EDANGER FISH所对应的name为Birds Bugs  Backyard Endangered Fish
  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 对象存储到缓存中;
      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数据较多时,页面的显示就会非常缓慢。......把这个title放在缓存中,每次取他就很快,但是缓存集合的对象没用到啊,兄弟

 

到目前为止:我们可以获取一个信息就是当缓存依赖发生改变的时候我们会把相对应的缓存项删除。我们现在要得到某个类的name。传进的参数是id,那么步骤为下:
1.这个id就是一个键,name就是这个键在缓存中对应的值,即缓存项,要得到这个name,先根据id进行拼凑,得到数组下标,然后得到这个缓存键,通过缓存键得到缓存项。若缓存项不空就直接返回这个缓存项,若为空,则说明之前的缓存依赖项发生了改变,那么就得重新获取这个缓存项,及其缓存依赖项。
2.获取缓存项,然后在逻辑层中通过得到一个接口对象引用来调用这个getnamebyid()方法。
  private static readonly IItem dal = PetShop.DALFactory.DataAccess.CreateItem();而这个方法是调用数据工厂类根据配置文件和反射来实例化一个类并且转换成接口类型的对象。
   string className = path + ".Item";
   return (PetShop.IDAL.IItem)Assembly.Load(path).CreateInstance(className)
3.获取缓存依赖.
首先抽象类继承了接口,然后抽象类通过子类传进来的参数,然后配合配置文件创建每个类对应的缓存对象,保存在一个AggregateCacheDependency的集合中,然后又向外提供一个访问这个缓存对象集合的方法GetDependency。
    第二:子类继承了抽象类,在子类中只有一个工作就是顺便实例化这个子类相对应的父类,参数是写死的,而实例化这个父类的构造函数的同时也就意味着实例化了这个父类即在这个类的父类中创建了这个类的缓存对象
    第三,定义一个工厂DependencyAccess根据配置文件和反射通过传进的每个类的名称再次实例化每个子类,只是这个时候对子类做了个小动作,把这个子类转换为接口引用,那么这个子类就只能实现接口的方法,即获取缓存对象的方法。原理是小指向大的,那么小的就为大中的小的,理解指针的概念哦(这个类中的方法目的是只能得到每个接口类型的引用,要得到缓存的话还要调用接口类型引用的GetDependency()方法)
    第四,上面的DependencyAccess通过public static IPetShopCacheDependency CreateItemDependency()方法只能得到接口对象的引用个,而不是如方法名那样得到缓存,所以我们定义DependencyFacade来通过封装上面的DependencyAcces定义一个名副其实的得到缓存的方法,如public static AggregateCacheDependency GetCategoryDependency()原理是通过调用上面的DependencyAccess中的每个接口对象引用的GetDependency()得到我们要的缓存集合
return DependencyAccess.CreateCategoryDependency().GetDependency(); 所以第四步只是对第三步的封装,得到如方法名那样的缓存对象而已,其他没什么了。

 

之前介绍了一个很平常的逻辑层,这个逻辑层没有数据缓存,现在定义一个:其实原理和取得page.title是一样的,只是这个时候的data不是name了,name是访问完数据访问层后的一个字段的名称,而我们现在的data是一个访问完数据访问层的一条或者多条完整的实体型信息集合。我们要用的是很只要把这个实体性信息集合,或者就一条实体型信息绑定到一个控件上即可。

不过这里多了一个东西就是它会判断配置文件中是否允许缓存,如果不允许的话就直接调用逻辑层的代码(逻辑层的代码没用到缓存,所以每次请求信息都要访问数据库),如果允许就往下执行缓存代码

 

然而,最理想的方式仍然是面向接口设计。根据第28章对ASP.NET缓存的分析,我们可以将表示层App_Code下的Proxy类与Utility类划分到业务逻辑层中,并修改这些静态类为实例类,并将这些类中与业务领域有关的方法抽象为接口,然后建立如数据访问层一样的抽象工厂。通过“依赖注入”方式,解除与具体领域对象类的依赖,使得表示层仅依赖于业务逻辑层的接口程序集以及工厂模块。

相关文章
相关标签/搜索