在学习设计模式中,你是否也曾经拿着一本介绍23种设计模式,啃概念、uml、实现方式,但之后感觉是看与没看没什么区别,这里有个例子,足够简单地让人感觉到设计的好处;
例子实现的功能:根据一个分类返回所有的商品,并缓存
例如 京东,根据笔记本分类id http://list.jd.com/list.html?cat=670,671,672
几个类图关系如下:
ProductService class:
public class ProductService
{
private ProductRepository _productRepository;
public ProductService()
{
_productRepository = new ProductRepository();
}
/// <summary>
///
/// </summary>
/// <param name="categoryId"></param>
/// <returns></returns>
public IList<Product> GetAllProByCategoryId(int categoryId)
{
IList<Product> products;
string cacheKey = string.Format("products_in_category_id_{0}",categoryId);
products = (IList<Product>)HttpContext.Current.Cache.Get(cacheKey);
if (products == null)
{
products = _productRepository.GetAllProByCategoryId(categoryId);
HttpContext.Current.Cache.Insert(cacheKey, products);
}
return products;
}
}
ProductRepository class:
public class ProductRepository
{
public IList<Product> GetAllProByCategoryId(int categoryId)
{
IList<Product> products = new List<Product>();
//get data for database
return products;
}
}
Product class:
public class Product
{
public int id { get; set; }
public string name { get; set; }
}
以上就简单实现了根据分类id 查询所有商品的功能,这里有几个问题:
- ProductService依赖于ProductRepository,ProductRepository的修改会影响它。
- ProductService不可测试,必须先实现ProductRepository里面操作数据库的方法,才能进行,紧耦合。
- 指定HTTP上下做缓存,之后难拓展,例如:之后需要换为Memcached或Redis做缓存,就要修改所有用到HTTP缓存的地方
用设计模式与面向对象设计原则解决以上问题
重构后:
ProductService class
public class ProductService
{
//解决问题1,重构ProductRepository令其基于接口,这里依赖于接口,不依赖于具体类:《依赖倒置原则》
private IProductRepository _productRepository;
//解决问题3,因为没有HTTP缓存的源码,不能按照基于接口的方式重构,可以用适配器(Adapter)模式转化为统一接口;
private ICacheStorage _cacheStorage;
//解决问题2,不创建实例,依赖外面传入:《依赖注入原则》
public ProductService(IProductRepository productRepository, ICacheStorage cacheStorage)
{
_productRepository = productRepository;
_cacheStorage = cacheStorage;
}
/// <summary>
/// 获取一个分类下的所有商品
/// </summary>
/// <param name="categoryId"></param>
/// <returns></returns>
public IList<Product> GetAllProByCategoryId(int categoryId)
{
IList<Product> products;
string cacheKey = string.Format("products_in_category_id_{0}", categoryId);
//products = (IList<Product>)HttpContext.Current.Cache.Get(cacheKey);
products = _cacheStorage.Get<IList<Product>>(cacheKey);
if (products == null)
{
products = _productRepository.GetAllProByCategoryId(categoryId);
//HttpContext.Current.Cache.Insert(cacheKey, products);
_cacheStorage.Add(cacheKey, products);
}
return products;
}
}
IProductRepository:
public interface IProductRepository
{
IList<Product> GetAllProByCategoryId(int categoryId);
}
ProductRepository:
public class ProductRepository : IProductRepository
{
public IList<Product> GetAllProByCategoryId(int categoryId)
{
IList<Product> products = new List<Product>();
//get data for database
return products;
}
}
ICacheStorage :
public interface ICacheStorage
{
void Delete(string key);
void Add(string key, object data);
T Get<T>(string key);
}
HttpCacheAdapter :
public class HttpCacheAdapter :ICacheStorage
{
public void Delete(string key)
{
HttpContext.Current.Cache.Remove(key);
}
public void Add(string key, object data)
{
HttpContext.Current.Cache.Insert(key, data);
}
public T Get<T>(string key)
{
return (T)HttpContext.Current.Cache.Get(key);
}
}
最后,添加一个ProductRepository模拟返回数据
public class ProductRepository_ForTest : IProductRepository
{
/// <summary>
/// 在数据库操作未完成情况下,使用返回模拟数据,可以继续测试Service层的逻辑;
/// </summary>
/// <param name="categoryId"></param>
/// <returns></returns>
public IList<Product> GetAllProByCategoryId(int categoryId)
{
IList<Product> products = new List<Product>();
Product one = new Product();
one.id = 1;
one.name = "AA";
products.Add(one);
one = new Product();
one.id = 2;
one.name = "BB";
products.Add(one);
return products;
}
}
两个方式如何使用呢?
使用控制台调用例子:
class Program
{
static void Main(string[] args)
{
//====== 没重构前调用 =======
int category = 1;
NoPatterns.ProductService noPatternsService = new NoPatterns.ProductService();
IList<NoPatterns.Product> products = noPatternsService.GetAllProByCategoryId(category);
//======= 重构后的调用 ======
//在数据库操作未完成情况下,可使用返回模拟数据;
YesPatterns.ProductRepository_ForTest productRepository = new ProductRepository_ForTest();
//基于数据库真实操作;
//YesPatterns.ProductRepository productRepository = new YesPatterns.ProductRepository();
//这样做的好处:数据库未准备好,也可以完成并测试Service层的逻辑,不用依赖;
//使用http上下缓存
YesPatterns.HttpCacheAdapter cache = new HttpCacheAdapter();
//使用Memcached缓存;
//YesPatterns.MemCachedAdapter cache = new MemCachedAdapter();
//这样做的好处:方便拓展,灵活,例如网站访问量大了,使用Http上下文缓存会力不从心,可以方便换为分布式的缓存,例如Memcached
//再例如:可以两种缓存方式一起使用。与用户相关缓存,使用Http;全局通用的缓存用Memcached;
YesPatterns.ProductService yesPatternsService = new YesPatterns.ProductService(productRepository, cache);
yesPatternsService.GetAllProByCategoryId(category);
}
}
完整例子代码已经放到github,点击前往