03 October 2015
In this post we are going to show a simple example of refactoring code to better adhere to the Single Responsibility Principle (SRP).
Let’s say we are building an application where one of the features is to process tweets of a specific user. We could write a class called MyService
and put a ProcessTweets
method in it, like so:
public class MyService
{
public void ProcessTweets(User user)
{
var twitterService = new TwitterService();
var tweets = twitterService.GetTweets(user);
// do something with tweets
}
}
Where TwitterService
is a placeholder and can be, for example, a .NET port of the Twitter API. Everything works as expected.
Now suppose that we want to implement some kind of caching. In the code above, the TwitterService.GetTweets
method always gets called. What we want to happen is to find a way to store the tweets of a User
the first time they are retrieved, so that next time, the data will be fetched from this cache rather than calling TwitterService.GetTweets
again.
One way to implement a cache is to use a static Dictionary
:
public class MyService
{
private static Dictionary<User, List<Tweet>> tweetsDictionary = new Dictionary<User, List<Tweet>>();
public void ProcessTweets(User user)
{
List<Tweet> tweets = null;
if (tweetsDictionary.ContainsKey(user))
{
tweets = tweetsDictionary[user];
}
else
{
var twitterService = new TwitterService();
tweets = twitterService.GetTweets(user);
tweetsDictionary.Add(user, tweets);
}
// do something with tweets
}
}
So now, the TwitterService.GetTweets
method only gets called the first time. On subsequent calls, the values stored from the Dictionary
will be returned. Everything works as expected.
In the first version of the code, the only responsibility of MyService
was to process tweets, and getting the tweets was straightforward. In the second version, which implements simple caching, another responsibility was added: the management of the cache.
To some, the management of the cache may not be a separate responsibility, but is instead a simple implementation detail of the “process tweets” requirement. But I do believe that it is a separate responsibility. Here is the way I think about it:
MyService
class). But as the code stands right now, any changes to the caching implementation will change the MyService
class.To better adhere to the SRP, we can create a separate class that takes care of the caching. Something like this:
public class TweetCache
{
private static Dictionary<User, List<Tweet>> tweetsDictionary = new Dictionary<User, List<Tweet>>();
public static List<Tweet> GetTweets(User user)
{
List<Tweet> tweets = null;
if (tweetsDictionary.ContainsKey(user))
{
tweets = tweetsDictionary[user];
}
else
{
var twitterService = new TwitterService();
tweets = twitterService.GetTweets(user);
tweetsDictionary.Add(user, tweets);
}
return tweets;
}
}
And then, MyService
can just make use of the TweetCache
class:
public class MyService
{
public void ProcessTweets(User user)
{
var tweets = TweetCache.GetTweets(user);
// do something with tweets
}
}
In this setup, changes to the caching implementation will only affect TweetCache
. MyService
will be unaffected.
In this post, we saw a simple example of a violation of the Single Responsibility Principle along with a possible solution. To keep things simple, the implemented solution is a helper class with a static method. There can be other solutions to the problem, however the main point of this post is to show that different responsibilities should belong to different classes.