在學習設計模式中,你是否也曾經拿着一本介紹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,點擊前往