You are going to look at a section of code that you might find in a typical e-commerce application specifically, the code that retrieves all products within a given category.
We have a ProductService class with the single GetAllProductsIn method, a Product class that represents the store's product, and a ProductRepository class that is used tp retrieve products from database.
The job of ProductService class is to coordinate the retrieval of a list of products from the repository for a given category ID and then store the resulta in cache so next call can be executed faster.
public class Product
{
}
public class ProductRepository
{
public IListGetAllProductsIn(int categoryId)
{
IListproducts = new List ();
return products;
}
}
public class ProductService
{
private ProductRepository _productRepository;
public ProductService()
{
_productRepository = new ProductRepository();
}
public IListGetAllProductsIn(int categoryId)
{
IListproducts;
string storageKey = String.Format("products in categoryId{0}", categoryId);
products = (List)HttpContext.Current.Cache.Get(storageKey);
if (products == null)
{
products = _productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
The Product and ProductRepository classes don’t require any explanation because they are simple placeholders in this scenario. The ProductService single method is straightforward and it simply coordinates the retrieval of products from the cache, and in the event of the cache being empty, the retrieval of products from the repository and the insertion into the cache.
So what’s wrong with the current codebase?
- The ProductService depends on the ProductRepository class. If the ProductRepository class changes its API, changes are going to need to be made in the ProductService class.
- The code is untestable. Without having a real ProductRepository class connecting to a real database, you’re unable to test the ProductService’s method because of the tight coupling that exists between these two classes. Another problem related to testing is the dependency on the HTTP context for use in the caching of the products. It is hard to test code that is so tightly coupled to HTTP context.
- You’re stuck with the HTTP context for caching. In its current state, using a different cache storage provider such as Velocity or Memcached would require altering of the ProductService class and any other class that uses caching. Velocity and Memcached are both distributed memory object caching systems that can be used in place of ASP.NET’s default caching mechanism.
Refactoring to Principles
First, consider the problem of the ProductService class dependency on the ProductRepository class. In its current state, the ProductService class is fragile; if the API of the ProductRepository class changes, the ProductService class might need to be modified. This breaks the separation of concerns and single responsibility principle.
The Dependency inversion Principle
Depend on abstractions, not on concretions.
We can employ the Dependency Inversion principle to decouple the ProductService class from the ProductRepository by having both depend on an abstraction — a n interface. Open the Product Repository class, right-click on the class name, and select Refactor ➪ Extract Interface from the context menu that appears. When the Extract Interface dialog appear, check the box next to the method name to ensure that it is included in the interface, and click OK. A new interface is created for you named IProductRepository. Clean up the code produced by including the System .Collections.Generic namespace as a using statement and marking the interface as public, which can be seen in the following code listing:
using System;
using System.Collections.Generic;
public interface IProductRepository
{
IList
}
The ProductRepository class is amended to implement the newly created interface, like so:
public class ProductRepository : IProductRepository
{
public IList
{
IList
return products;
}
}
The last thing you need to do is update the ProductService class to ensure that it references the interface rather than the concrete implementation:
public class ProductService
{
private IProductRepository _productRepository;
public ProductService()
{
_productRepository = new ProductRepository();
}
public IList
{
IList
string storageKey = String.Format("products in categoryId{0}", categoryId);
products = (List
if (products == null)
{
products = _productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
What have you achieved by introducing a new interface? The ProductService class now depends only on an abstraction rather than a concrete implementation; this means that the ProductService class is completely ignorant of any implementation, ensuring that it is less fragile and the code base as a whole is less resilient to change.
However, there is one slight problem: the ProductService class is still responsible for creating the concrete implementation and currently it is impossible to test the code without a valid ProductRepository class. Dependency Injection can help here.
public class ProductService
{
private IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public IList
{
IList
string storageKey = String.Format("products in categoryId{0}", categoryId);
products = (List
if (products == null)
{
products = _productRepository.GetAllProductsIn(categoryId);
HttpContext.Current.Cache.Insert(storageKey, products);
}
return products;
}
}
This enables a substitute to be passed to the ProductService class during testing, which enables you to test the ProductService class in isolation. By removing the responsibility of obtaining dependencies from the ProductService, you are ensuring that the ProductService class adheres to the Single Responsibility principle: it is now only concerned with the coordinating of retrieving data from the cache or repository and not for creating the concrete IProductRepository implementation.
Dependency Injection comes in three flavors: Constructer, Method, and Property. You have just used Constructor Injection. The last thing you need to do is sort out the dependency on the HTTP Context for your caching requirements. For this you will employ the services of a simple design pattern.
Hiç yorum yok:
Yorum Gönder