运用设计原则编写可测试性的代码
初入编程开发一两个项目后,渐渐发现自己编写的代码越往后越复杂,一个类承担太多的职责每次改动都需要先理清代码逻辑,还承担着很大风险。
接下来通过一个例子展示下这样编写可维护、可测试的代码。
需求
- 根据
Category
查询Product
集合 - 对每次查询的结果加入缓存
不好的代码
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
public class ProductRepository
{
public IList<Product> GetAllProductsIn(int categoryId)
{
IList<Product> products = new List<Product>();
// Database operation to populate products …
return products;
}
}
public class ProductService
{
private ProductRepository _productRepository;
public ProductService()
{
_productRepository = new ProductRepository();
}
public IList<Product> GetAllProductsIn(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.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
这种编程所带来的问题
ProductService
依赖ProductRepository
类,如果ProductRepository
中API修改ProductService
也必须修改- 代码不单元测试,因
ProductService
依赖ProductRepository
和存储HttpContext.Current.Cache
- 如果需要利用另外的方式缓存,需要修改
ProductService
中方法
健壮的代码
- 依赖倒置原则,高层应依赖于抽象,不应依赖具体的底层。
- 依赖注入,依赖的对象在使用时注入对象,这样可以
Mock
类单元测试。
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
public interface IProductRepository
{
IList<Product> GetAllProductsIn(string categoryName);
}
public interface ICacheStorage
{
void Remove(string key);
void Store(string key, object data);
T Retrieve<T>(string storageKey);
}
public class ProductService
{
private IProductRepository _productRepository;
private ICacheStorage _cacheStorage;
public ProductService(IProductRepository productResitory, ICacheStorage cacheStorage)
{
_productRepository = productResitory;
_cacheStorage = cacheStorage;
}
public IList<Product> GetAllProductsIn(string categoryName)
{
IList<Product> products;
string storageKey = string.Format("products_in_category_id_{0}", categoryName);
products = _cacheStorage.Retrieve<IList<Product>>(storageKey);
if (products == null)
{
products = _productRepository.GetAllProductsIn(categoryName);
_cacheStorage.Store(storageKey, products);
}
return products;
}
}
具体的仓储项目
public class DataContext
{
private List<Product> _products;
public DataContext()
{
_products = new List<Product>();
_products.Add(new Product { Id = 1, Name = "BaseBall Cap", Price = 9.99m, Category = "Hats" });
_products.Add(new Product { Id = 2, Name = "Flat Cap", Price = 5.99m, Category = "Gloves" });
_products.Add(new Product { Id = 3, Name = "Top Hat", Price = 6.99m, Category = "Scarfs" });
_products.Add(new Product { Id = 4, Name = "Mitten", Price = 10.99m, Category = "Hats" });
_products.Add(new Product { Id = 5, Name = "Fingerless Glove", Price = 13.99m, Category = "Gloves" });
_products.Add(new Product { Id = 6, Name = "Leather Glove", Price = 7.99m, Category = "Scarfs" });
_products.Add(new Product { Id = 7, Name = "Silk Scarf", Price = 23.99m, Category = "Scarfs" });
_products.Add(new Product { Id = 8, Name = "Woollen", Price = 14.99m, Category = "Hats" });
}
public List<Product> Products
{
get { return _products; }
}
}
public class ProductRepository:IProductRepository
{
private DataContext _dataContext;
public ProductRepository()
{
_dataContext = new DataContext();
}
public IList<Product> GetAllProductsIn(string categoryName)
{
var result = _dataContext.Products.FindAll(i=>i.Category == categoryName);
return result;
}
}
单元测试
参考下篇博客 AutoFac依赖注入应用