In a
previous article, I discussed the benefits of passing interfaces as parameters to a method instead of using concrete classes. Now I’m going to discuss other ways in which using interfaces can help future-proof your code, make it testable, and more extensible.
First, let’s define some terms. A dependency is something that code depends on in a class. For example, a business logic layer [usually] depends on calls to a data-access layer. Take this code as an example:
public static class ArticleService
{
public static IList<Article> GetActive()
{
var articles = ArticleRespository.Get();
return articles.Where(article => article.IsActive).ToList();
}
}
This simple article service calls the article repository (the data-access layer), returns a list of articles, and then filters them based on the logic that we only want the active articles. A few things stand out with this code. First, we have a static dependency on the article repository; they are tightly coupled. If we need to switch from using SQL in the data-access layer to XML-based storage, either we will have to completely rewrite the ArticleRepository.Get() method, or we will have to write a new method and then change the GetActive() method to point to that one instead. The service knows too much about the repository.
Additionally, the service itself is static. The calling code of the article service class looks like this:
private void BindArticles()
{
uxArticleView.DataSource = ArticleService.GetActive();
uxArticleView.DataBind();
}
If you wanted to write a unit test to ensure that all active articles were being returned from the article service, how would you do it? Well, for the purposes of this article, you can’t (you could use something like TypeMocks). The static dependencies between all of these classes would force your unit test to have to set up a database connection. It probably can’t even do that because no connection strings are being passed into the repository.
Static dependencies are usually bad. They make unit testing extremely difficult and can cause maintenance nightmares down the road. So how do we fix this situation?
First, let’s rewrite our classes to depend on interfaces. Here is our article service interface:
public interface IArticleService
{
IList<Article> GetActive();
}
And our article repository interface:
public interface IArticleRepository
{
IList<Article> Get();
}
Now we can rewrite the article service to implement its interface:
public class ArticleService : IArticleService
{
public IList<Article> GetActive()
{
var articles = ArticleRespository.Get();
return articles.Where(article => article.IsActive).ToList();
}
}
And we can refactor again to remove the static dependency. We do this by overloading the constructor for the service class:
public class ArticleService : IArticleService
{
private readonly IArticleRepository _articleRepository;
public ArticleService(IArticleRepository articleRepository)
{
_articleRepository = articleRepository;
}
public IList<Article> GetActive()
{
var articles = _articleRepository.Get();
return articles.Where(article => article.IsActive).ToList();
}
}
As you can see, the static call to the repository has been replaced by an interface call. In essence, we are saying that the article service depends on the article repository, so when you create the article service, you must pass in a class that implements IArticleRepository.
How does this work? Well, if a class implements an interface, it must implement all the methods of the interface. This means that we can call the Get() method and the code that runs will be whichever class you chose to pass in the constructor. In our example, I talked about how switching the storage from SQL to XML. Let’s see what would happen using interfaces. First, let’s define a SQL implementation:
public class ArticleSqlRepository : IArticleRepository
{
public IList<Article> Get()
{
//define SqlConnection
//call the database
//fill out the article list
return articles;
}
}
Our calling code has now changed. Let’s review:
private void BindArticles()
{
IArticleService articleService = new ArticleService(new ArticleSqlRepository());
uxArticleView.DataSource = articleService.GetActive();
uxArticleView.DataBind();
}
As you can see, the article service is defined by passing in an instance of the article SQL repository. This will now compile and work perfectly. Additionally, it is extremely easy to change from SQL to XML. Simply create a new class and pass that in:
public class ArticleXmlRepository : IArticleRepository
{
public IList<Article> Get()
{
//define XmlReader
//open the xml file
//read the xml into the article list
return articles;
}
}
private void BindArticles()
{
IArticleService articleService = new ArticleService(new ArticleXmlRepository());
uxArticleView.DataSource = articleService.GetActive();
uxArticleView.DataBind();
}
Our work is still not over. The calling code is a bit ugly, and having to instantiate services and repositories all the time will be take performance hit. Additionally, any changes to the dependencies would require a recompile which isn’t a huge issue, but still not ideal.
In the next article, I will cover dependency injection and how we can use it to automatically resolve the dependencies through a configuration file. Play around with the examples above (if you need to) to get a feel for it, and the next article will make a lot more sense.