首先,我们虚拟一个系统环境(e-Shop),即网上商店的应用程序,其中有一个应用: 获取指定分类下的所有产品信息。我们按照传统的思路来实现。图1展示了系统的设计图。
新建一个类库工程EShop.Service. 然后添加相应的类到工程中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
namespace EShop.Service
{
public class Product
{
}
public class ProductRepository
{
public IList<Product> GetProductsByCategory(int categoryId)
{
IList<Product> products = new List<Product>();
//进行数据库操作
return products;
}
}
public class ProductService
{
private ProductRepository productRepository;
public ProductService()
{
productRepository = new ProductRepository();
}
public IList<Product> GetProductsByCategory(int categoryId)
{
IList<Product> products;
string storageKey = String.Format("products_in_category_id_{0}", categoryId);
products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
if (products == null)
{
products = productRepository.GetProductsByCategory(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
}
从以上的程序段是否能发现不合理之处呢?我归纳了一下,大致有以下几点:
1. ProductService依赖于ProductRepository,一旦后者的API发生变化,则前者必然跟着发生变化。
2. 难以测试ProductService的方法,因为ProductRepository并未真正连接到数据库,另外还依赖于HttpContext,两者之间紧耦合。
3. 目前采用HttpContext来进行缓存,如果要更换缓存机制(如:Velocity或Memcached),ProductService将进行更改。
基于以上几点不合理之处,我们将一一进行重构和优化。
针对1:我们采用依赖倒置原则(Dependency Inversion Principle)——依赖于抽象而不是具体实现来解决。我们加入了接口IProductRepository。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public interface IProductRepository
{
IList<Product> GetProductsByCategory(int categoryId);
}
public class ProductRepository : IProductRepository
{
//...
}
public class ProductService
{
private IProductRepository productRepository;
public ProductService()
{
productRepository = new ProductRepository();
}
//...
}
针对2:我们采用依赖注入原则(Dependency Injection Principle)——通过将抽象注入到构造函数、方法或属性来解决。我们修改ProductService构造函数。
注入构造函数
public class ProductService
{
private IProductRepository productRepository;
public ProductService(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
//...
}
针对3:我们采用适配器模式(Adapter Pattern)——转换已有接口与客户期望的目标接口使之兼容来解决。我们添加了接口ICacheStorage和类HttpContextCacheAdapter。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public interface ICacheStorage
{
void Remove(string key);
void Store(string key, object data);
T Retrieve<T>(string key);
}
public class HttpContextCacheAdapter : ICacheStorage
public class HttpContextCacheAdapter : ICacheStorage
{
public void Remove(string key)
{
HttpContext.Current.Cache.Remove(key);
}
public void Store(string key, object data)
{
HttpContext.Current.Cache.Insert(key, data);
}
public T Retrieve<T>(string key)
{
T item = (T)HttpContext.Current.Cache.Get(key);
if (item == null)
{
item = default(T);
}
return item;
}
}
public class ProductService
{
private IProductRepository productRepository;
private ICacheStorage cacheStorage;
public ProductService(IProductRepository productRepository, ICacheStorage cacheStorage)
{
this.productRepository = productRepository;
this.cacheStorage = cacheStorage;
}
public IList<Product> GetProductsByCategory(int categoryId)
{
IList<Product> products;
string storageKey = String.Format("products_in_category_id_{0}", categoryId);
products = cacheStorage.Retrieve<List<Product>>(storageKey);
if (products == null)
{
products = productRepository.GetProductsByCategory(categoryId);
cacheStorage.Store(storageKey, products);
}
return products;
}
}
综合以上的解决方案,这里贴出重构后完整的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
namespace EShop.Service
{
public class Product
{
}
public interface IProductRepository
{
IList<Product> GetProductsByCategory(int categoryId);
}
public class ProductRepository : IProductRepository
{
public IList<Product> GetProductsByCategory(int categoryId)
{
IList<Product> products = new List<Product>();
//进行数据库操作
return products;
}
}
public interface ICacheStorage
{
void Remove(string key);
void Store(string key, object data);
T Retrieve<T>(string key);
}
public class HttpContextCacheAdapter : ICacheStorage
{
public void Remove(string key)
{
HttpContext.Current.Cache.Remove(key);
}
public void Store(string key, object data)
{
HttpContext.Current.Cache.Insert(key, data);
}
public T Retrieve<T>(string key)
{
T item = (T)HttpContext.Current.Cache.Get(key);
if (item == null)
{
item = default(T);
}
return item;
}
}
//有时为方便起见,我们定义空对象(空缓存适配器)
public class NullCacheAdapter : ICacheStorage
{
public void Remove(string key)
{
}
public void Store(string key, object data)
{
}
public T Retrieve<T>(string key)
{
return default(T);
}
}
public class ProductService
{
private IProductRepository productRepository;
private ICacheStorage cacheStorage;
public ProductService(IProductRepository productRepository, ICacheStorage cacheStorage)
{
this.productRepository = productRepository;
this.cacheStorage = cacheStorage;
}
public IList<Product> GetProductsByCategory(int categoryId)
{
IList<Product> products;
string storageKey = String.Format("products_in_category_id_{0}", categoryId);
products = cacheStorage.Retrieve<List<Product>>(storageKey);
if (products == null)
{
products = productRepository.GetProductsByCategory(categoryId);
cacheStorage.Store(storageKey, products);
}
return products;
}
}
}
附上重构后的设计图: